Finish v0.8.5

This commit is contained in:
manuroe
2019-05-03 12:32:05 +02:00
140 changed files with 7032 additions and 2452 deletions
+18 -1
View File
@@ -1,9 +1,26 @@
Changes in 0.8.5 (2019-xx-xx)
Changes in 0.8.5 (2019-05-03)
===============================================
Improvements:
* Upgrade MatrixKit version ([v0.9.9](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.9.9)).
* Push: Add more logs to track spontaneously disabling (#2348).
* Widgets: Use scalar prod urls in Riot mobile apps (#2349).
* Productiviy: Create templates (see Tools/Templates/README.md).
* Notifications: Use UserNotifications framework for local notifications (iOS 10+), thanks to @fridtjof (PR #2207).
* Notifications: Added titles to notifications on iOS 10+, thanks to @fridtjof (PR #2347).
* iOS 12 Notification: Group them by room (#2337 and PR #2347 thanks to @fridtjof).
* Notifications: When navigate to a room, remove associated delivered notifications (#2337).
* Key backup: Adjust wording for untrusted backup to match Riot Web.
* Jitsi integration: Use the matching WebRTC framework (#1483).
* Fastlane: Set iCloud container environment (PR #2385).
* Remove code used for iOS 9 only (PR #2386).
Bug fix:
* Share extension: Fix a crash when receive a memory warning (PR #2352).
* Upgraded rooms show up in the share extension twice (#2293).
* +N read receipt text is invisible on dark theme (#2294).
* Avoid crashes with tableview reload animation in settings and room settings (PR #2364).
* Media picker: Fix some retain cycles (PR #2382).
Changes in 0.8.4 (2019-03-21)
===============================================
+2 -5
View File
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, "9.0"
platform :ios, '10.0'
# Use frameforks to allow usage of pod written in Swift (like PiwikTracker)
use_frameworks!
@@ -7,7 +7,7 @@ use_frameworks!
# Different flavours of pods to MatrixKit
# The current MatrixKit pod version
$matrixKitVersion = '0.9.8'
$matrixKitVersion = '0.9.9'
# The develop branch version
#$matrixKitVersion = 'develop'
@@ -43,18 +43,15 @@ def import_MatrixKitAppExtension
if $matrixKitVersion == 'local'
pod 'MatrixSDK', :path => '../matrix-ios-sdk/MatrixSDK.podspec'
pod 'MatrixSDK/SwiftSupport', :path => '../matrix-ios-sdk/MatrixSDK.podspec'
pod 'MatrixSDK/JingleCallStack', :path => '../matrix-ios-sdk/MatrixSDK.podspec'
pod 'MatrixKit/AppExtension', :path => '../matrix-ios-kit/MatrixKit.podspec'
else
if $matrixKitVersion == 'develop'
pod 'MatrixSDK', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop'
pod 'MatrixSDK/SwiftSupport', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop'
pod 'MatrixSDK/JingleCallStack', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop'
pod 'MatrixKit/AppExtension', :git => 'https://github.com/matrix-org/matrix-ios-kit.git', :branch => 'develop'
else
pod 'MatrixKit/AppExtension', $matrixKitVersion
pod 'MatrixSDK/SwiftSupport'
pod 'MatrixSDK/JingleCallStack'
end
end
end
+28 -28
View File
@@ -42,46 +42,47 @@ PODS:
- GBDeviceInfo/Core (5.2.0)
- GZIP (1.2.2)
- HPGrowingTextView (1.1)
- JitsiMeetSDK (2.1.0)
- libbase58 (0.1.4)
- libPhoneNumber-iOS (0.9.13)
- MatrixKit (0.9.8):
- MatrixKit (0.9.9):
- cmark (~> 0.24.1)
- DTCoreText (~> 1.6.21)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixKit/Core (= 0.9.8)
- MatrixSDK (= 0.12.4)
- MatrixKit/AppExtension (0.9.8):
- MatrixKit/Core (= 0.9.9)
- MatrixSDK (= 0.12.5)
- MatrixKit/AppExtension (0.9.9):
- cmark (~> 0.24.1)
- DTCoreText (~> 1.6.21)
- DTCoreText/Extension
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.12.4)
- MatrixKit/Core (0.9.8):
- MatrixSDK (= 0.12.5)
- MatrixKit/Core (0.9.9):
- cmark (~> 0.24.1)
- DTCoreText (~> 1.6.21)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.12.4)
- MatrixSDK (0.12.4):
- MatrixSDK/Core (= 0.12.4)
- MatrixSDK/Core (0.12.4):
- MatrixSDK (= 0.12.5)
- MatrixSDK (0.12.5):
- MatrixSDK/Core (= 0.12.5)
- MatrixSDK/Core (0.12.5):
- AFNetworking (~> 3.2.0)
- GZIP (~> 1.2.2)
- libbase58 (~> 0.1.4)
- OLMKit (~> 3.0.0)
- OLMKit (~> 3.1.0)
- Realm (~> 3.13.1)
- MatrixSDK/JingleCallStack (0.12.4):
- MatrixSDK/JingleCallStack (0.12.5):
- JitsiMeetSDK (~> 2.1.0)
- MatrixSDK/Core
- WebRTC (= 63.11.20455)
- MatrixSDK/SwiftSupport (0.12.4):
- MatrixSDK/SwiftSupport (0.12.5):
- MatrixSDK/Core
- OLMKit (3.0.0):
- OLMKit/olmc (= 3.0.0)
- OLMKit/olmcpp (= 3.0.0)
- OLMKit/olmc (3.0.0)
- OLMKit/olmcpp (3.0.0)
- OLMKit (3.1.0):
- OLMKit/olmc (= 3.1.0)
- OLMKit/olmcpp (= 3.1.0)
- OLMKit/olmc (3.1.0)
- OLMKit/olmcpp (3.1.0)
- PiwikTracker (4.4.2):
- PiwikTracker/Core (= 4.4.2)
- PiwikTracker/Core (4.4.2)
@@ -95,15 +96,14 @@ PODS:
- Reusable/View (4.0.5)
- SwiftGen (6.1.0)
- SwiftLint (0.30.1)
- WebRTC (63.11.20455)
- zxcvbn-ios (1.0.4)
DEPENDENCIES:
- cmark
- DTCoreText
- GBDeviceInfo (~> 5.2.0)
- MatrixKit (= 0.9.8)
- MatrixKit/AppExtension (= 0.9.8)
- MatrixKit (= 0.9.9)
- MatrixKit/AppExtension (= 0.9.9)
- MatrixSDK/JingleCallStack
- MatrixSDK/SwiftSupport
- OLMKit
@@ -122,6 +122,7 @@ SPEC REPOS:
- GBDeviceInfo
- GZIP
- HPGrowingTextView
- JitsiMeetSDK
- libbase58
- libPhoneNumber-iOS
- MatrixKit
@@ -131,7 +132,6 @@ SPEC REPOS:
- Reusable
- SwiftGen
- SwiftLint
- WebRTC
- zxcvbn-ios
EXTERNAL SOURCES:
@@ -152,19 +152,19 @@ SPEC CHECKSUMS:
GBDeviceInfo: 2c65ceb9404f9079264d4c238f5b81916fdfc5e2
GZIP: 12374d285e3b5d46cfcd480700fcfc7e16caf4f1
HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19
JitsiMeetSDK: 3e66564af7f38a19142338955dd7f581801852b3
libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd
libPhoneNumber-iOS: e444379ac18bbfbdefad571da735b2cd7e096caa
MatrixKit: 098ddd270d1dec86a85ea93a85a00e37b2d80c11
MatrixSDK: 310efb69f70b4d7772f6f134b06fbb9ec238e6a7
OLMKit: 88eda69110489f817d59bcb4353b7c247570aa4f
MatrixKit: 6f553797e1ad42794b5336afb5cecb975ec69daa
MatrixSDK: ed0d0cee4877955052f19730bb3ee727e01ec948
OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639
PiwikTracker: 42862c7b13028065c3dfd36b4dc38db8a5765acf
Realm: 50071da38fe079e0735e47c9f2eae738c68c5996
Reusable: 188be1a54ac0691bc66e5bb24ec6eb91971b315b
SwiftGen: f872ca75cbd17bf7103c17f13dcfa0d9a15667b0
SwiftLint: a54bf1fe12b55c68560eb2a7689dfc81458508f7
WebRTC: f2a6203584745fe53532633397557876b5d71640
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
PODFILE CHECKSUM: 63fb661f957b3fa4c00765bc4d28bed41892847c
PODFILE CHECKSUM: cfb6be050dfbb227d58b14434629e447ea54554b
COCOAPODS: 1.6.1
+1 -1
View File
@@ -24,7 +24,7 @@ CocoaPods command::
$ pod install
This will load all dependencies for the Riot source code, including MatrixKit
and MatrixSDK. You will need an recent and updated (``pod update``) install of
and MatrixSDK. You will need an recent and updated (``pod setup``) install of
CocoaPods.
Then, open ``Riot.xcworkspace`` with Xcode
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -17,6 +17,7 @@
#import <UIKit/UIKit.h>
#import <MatrixKit/MatrixKit.h>
#import <UserNotifications/UserNotifications.h>
#import "MasterTabBarController.h"
#import "JitsiViewController.h"
@@ -37,7 +38,7 @@ extern NSString *const kAppDelegateDidTapStatusBarNotification;
*/
extern NSString *const kAppDelegateNetworkStatusDidChangeNotification;
@interface AppDelegate : UIResponder <UIApplicationDelegate, MXKCallViewControllerDelegate, UISplitViewControllerDelegate, UINavigationControllerDelegate, JitsiViewControllerDelegate>
@interface AppDelegate : UIResponder <UIApplicationDelegate, MXKCallViewControllerDelegate, UISplitViewControllerDelegate, UINavigationControllerDelegate, JitsiViewControllerDelegate, UNUserNotificationCenterDelegate>
{
BOOL isPushRegistered;
@@ -119,7 +120,7 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification;
Log out all the accounts without confirmation.
Show the authentication screen on successful logout.
@param sendLogoutRequest Indicate whether send logout request to home server.
@param sendLogoutRequest Indicate whether send logout request to homeserver.
@param completion the block to execute at the end of the operation.
*/
- (void)logoutSendingRequestServer:(BOOL)sendLogoutServerRequest
@@ -143,6 +144,11 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification;
#pragma mark - Matrix Room handling
// Show a room and jump to the given event if event id is not nil otherwise go to last messages.
- (void)showRoom:(NSString*)roomId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession restoreInitialDisplay:(BOOL)restoreInitialDisplay completion:(void (^)(void))completion;
- (void)showRoom:(NSString*)roomId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession restoreInitialDisplay:(BOOL)restoreInitialDisplay;
- (void)showRoom:(NSString*)roomId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession;
// Creates a new direct chat with the provided user id
+649 -213
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -25,9 +25,11 @@
<key>matrixApps</key>
<true/>
<key>integrationsUiUrl</key>
<string>https://scalar-staging.riot.im/scalar-web/</string>
<string>https://scalar.vector.im/</string>
<key>integrationsRestUrl</key>
<string>https://scalar-staging.riot.im/scalar/api</string>
<string>https://scalar.vector.im/api</string>
<key>jitsiServerURL</key>
<string>https://jitsi.riot.im</string>
<key>integrationsWidgetsUrls</key>
<array>
<string>https://scalar-staging.riot.im/scalar/api</string>
+3 -3
View File
@@ -282,11 +282,11 @@
"auth_add_email_and_phone_message" = "Füge eine E-Mail-Adresse und eine Telefonnummer hinzu, damit dich andere Benutzer finden können. Über die E-Mail-Adresse kannst du das Passwort zurücksetzen.";
"auth_use_server_options" = "Individuelle Server Optionen";
"auth_email_validation_message" = "Prüfe dein E-Mail-Konto um mit der Registrierung fortzufahren";
"auth_recaptcha_message" = "Dieser Home-Server will sicherstellen dass du kein Robot bist";
"auth_recaptcha_message" = "Dieser Homeserver will sicherstellen dass du kein Robot bist";
"auth_reset_password_message" = "E-Mail-Adresse angeben, um das Passwort zurückzusetzen:";
"auth_reset_password_missing_email" = "Die E-Mail-Adresse die mit dem Konto verbunden ist muss eingegeben werden.";
"auth_reset_password_error_unauthorized" = "Konnte E-Mail-Adresse nicht verifizieren. Klicke den Link in der Registrierungs-E-Mail";
"auth_reset_password_error_not_found" = "Diese E-Mail-Adresse ist nicht mit einer Matrix-ID auf diesem Home-Server verknüpft.";
"auth_reset_password_error_not_found" = "Diese E-Mail-Adresse ist nicht mit einer Matrix-ID auf diesem Homeserver verknüpft.";
"auth_reset_password_success_message" = "Dein Passwort wurde zurückgesetzt.\n\nDu wurdest von allen Geräten abgemeldet, diese bekommen jetzt keine Push-Benachrichtigungen mehr. Um diese wiedereinzuschalten, melde dich auf den Geräten erneut an.";
"auth_add_email_and_phone_warning" = "Registrierung mit E-Mail und Telefonnummer zugleich ist noch nicht unterstützt. Nur die Telefonnummer wird berücksichtigt. Du kannst deine E-Mail-Adresse in deinem Profil ergänzen.";
"room_creation_make_public_prompt_msg" = "Sicher, dass du diesen Raum öffentlich machen willst? Jeder kann deine Nachrichten lesen und dem Raum beitreten.";
@@ -368,7 +368,7 @@
"directory_server_picker_title" = "Wähle ein Verzeichnis";
"directory_server_all_rooms" = "Alle Räume auf %@ Server";
"directory_server_all_native_rooms" = "Alle nativen Matrix-Räume";
"directory_server_type_homeserver" = "Gib einen Home-Server ein, um davon öffentliche Räume zu listen";
"directory_server_type_homeserver" = "Gib einen Homeserver ein, um davon öffentliche Räume zu listen";
"directory_server_placeholder" = "matrix.org";
// Others
"or" = "oder";
+1
View File
@@ -19,4 +19,5 @@
"NSPhotoLibraryUsageDescription" = "The photo library is used to send photos and videos.";
"NSMicrophoneUsageDescription" = "The microphone is used to take videos, make calls.";
"NSContactsUsageDescription" = "In order to show you which of your contacts are already using Riot or Matrix, we can send the email addresses and phone numbers in your address book to your Matrix Identity Server. New Vector does not store this data or use it for any other purpose. For more information please see the privacy policy page in application settings.";
"NSCalendarsUsageDescription" = "See your scheduled meetings in the app.";
+8 -2
View File
@@ -14,10 +14,13 @@
limitations under the License.
*/
/* Message title for a specific person in a named room */
"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@";
/** Single, end-to-end encrypted messages (ie. we don't know what they say) */
/* New message from a specific person, not referencing a room */
"MSG_FROM_USER" = "Message from %@";
"MSG_FROM_USER" = "%@ sent a message";
/* New message from a specific person in a named room */
"MSG_FROM_USER_IN_ROOM" = "%@ posted in %@";
@@ -39,7 +42,7 @@
/** Image Messages **/
/* New action message from a specific person, not referencing a room. */
"IMAGE_FROM_USER" = "%@ sent you a picture %@";
"IMAGE_FROM_USER" = "%@ sent a picture %@";
/* New action message from a specific person in a named room. */
"IMAGE_FROM_USER_IN_ROOM" = "%@ posted a picture %@ in %@";
@@ -50,6 +53,9 @@
/* A single unread message */
"SINGLE_UNREAD" = "You received a message";
/* Sticker from a specific person, not referencing a room. */
"STICKER_FROM_USER" = "%@ sent a sticker";
/** Coalesced messages **/
/* Multiple unread messages in a room */
+105 -9
View File
@@ -83,7 +83,7 @@
"auth_invalid_email" = "This doesn't look like a valid email address";
"auth_invalid_phone" = "This doesn't look like a valid phone number";
"auth_missing_password" = "Missing password";
"auth_add_email_message" = "Add an email address to your account to let users discover you, and let you reset password.";
"auth_add_email_message" = "Add an email address to your account to let users discover you, and to reset your password.";
"auth_add_phone_message" = "Add a phone number to your account to let users discover you.";
"auth_add_email_phone_message" = "Add an email address and/or a phone number to your account to let users discover you. Email address will also let you reset your password.";
"auth_add_email_and_phone_message" = "Add an email address and a phone number to your account to let users discover you. Email address will also let you reset your password.";
@@ -102,14 +102,14 @@
"auth_msisdn_validation_title" = "Verification Pending";
"auth_msisdn_validation_message" = "We\'ve sent an SMS with an activation code. Please enter this code below.";
"auth_msisdn_validation_error" = "Unable to verify phone number.";
"auth_recaptcha_message" = "This Home Server would like to make sure you are not a robot";
"auth_recaptcha_message" = "This homeserver would like to make sure you are not a robot";
"auth_reset_password_message" = "To reset your password, enter the email address linked to your account:";
"auth_reset_password_missing_email" = "The email address linked to your account must be entered.";
"auth_reset_password_missing_password" = "A new password must be entered.";
"auth_reset_password_email_validation_message" = "An email has been sent to %@. Once you've followed the link it contains, click below.";
"auth_reset_password_next_step_button" = "I have verified my email address";
"auth_reset_password_error_unauthorized" = "Failed to verify email address: make sure you clicked the link in the email";
"auth_reset_password_error_not_found" = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
"auth_reset_password_error_not_found" = "Your email address does not appear to be associated with a Matrix ID on this homeserver.";
"auth_reset_password_success_message" = "Your password has been reset.\n\nYou have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, re-log in on each device.";
"auth_add_email_and_phone_warning" = "Registration with email and phone number at once is not supported yet until the api exists. Only the phone number will be taken into account. You may add your email to your profile in settings.";
"auth_accept_policies" = "Please review and accept the policies of this homeserver:";
@@ -326,7 +326,7 @@
"settings_mark_all_as_read" = "Mark all messages as read";
"settings_report_bug" = "Report bug";
"settings_clear_cache" = "Clear cache";
"settings_config_home_server" = "Home server is %@";
"settings_config_home_server" = "Homeserver is %@";
"settings_config_identity_server" = "Identity server is %@";
"settings_config_user_id" = "Logged in as %@";
@@ -431,11 +431,11 @@
"settings_key_backup_info" = "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.";
"settings_key_backup_info_checking" = "Checking...";
"settings_key_backup_info_none" = "Your keys are not being backed up from this device.";
"settings_key_backup_info_signout_warning" = "Back up your keys before signing out to avoid losing them.";
"settings_key_backup_info_signout_warning" = "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.";
"settings_key_backup_info_version" = "Key Backup Version: %@";
"settings_key_backup_info_algorithm" = "Algorithm: %@";
"settings_key_backup_info_valid" = "This device is backing up your keys.";
"settings_key_backup_info_not_valid" = "This device is not backing up your keys.";
"settings_key_backup_info_not_valid" = "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.";
"settings_key_backup_info_progress" = "Backing up %@ keys...";
"settings_key_backup_info_progress_done" = "All keys backed up";
@@ -449,7 +449,7 @@
"settings_key_backup_button_create" = "Start using Key Backup";
"settings_key_backup_button_restore" = "Restore from Backup";
"settings_key_backup_button_delete" = "Delete Backup";
"settings_key_backup_button_use" = "Use key backup";
"settings_key_backup_button_connect" = "Connect this device to Key Backup";
"settings_key_backup_delete_confirmation_prompt_title" = "Delete Backup";
"settings_key_backup_delete_confirmation_prompt_msg" = "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.";
@@ -694,7 +694,7 @@
"key_backup_setup_intro_title" = "Never lose encrypted messages";
"key_backup_setup_intro_info" = "Messages in encrypted rooms are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.\n\nSecurely back up your keys to avoid losing them.";
"key_backup_setup_intro_setup_action_without_existing_backup" = "Start using Key Backup";
"key_backup_setup_intro_setup_action_with_existing_backup" = "Use Key Backup";
"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Connect this device to Key Backup";
"key_backup_setup_intro_manual_export_info" = "(Advanced)";
"key_backup_setup_intro_manual_export_action" = "Manually export keys";
@@ -773,7 +773,7 @@
// Recover
"key_backup_recover_banner_title" = "Never lose encrypted messages";
"key_backup_recover_banner_subtitle" = "Use Key Backup";
"key_backup_recover_connent_banner_subtitle" = "Connect this device to Key Backup";
// MARK: Sign out warning
@@ -792,3 +792,99 @@
"sign_out_key_backup_in_progress_alert_title" = "Key backup in progress. If you sign out now youll lose access to your encrypted messages.";
"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "I don't want my encrypted messages";
"sign_out_key_backup_in_progress_alert_cancel_action" = "I'll wait";
// MARK: - Device Verification
"device_verification_title" = "Verify device";
"device_verification_security_advice" = "For maximum security, we recommend you do this in person or use another trusted means of communication";
"device_verification_cancelled" = "The other party cancelled the verification.";
"device_verification_cancelled_by_me" = "The verification has been cancelled. Reason: %@";
"device_verification_error_cannot_load_device" = "Cannot load device information.";
// Mark: Incoming
"device_verification_incoming_title" = "Incoming Verification Request";
"device_verification_incoming_description_1" = "Verify this device to mark it as trusted. Trusting devices of partners gives you extra peace of mind when using end-to-end encrypted messages.";
"device_verification_incoming_description_2" = "Verifying this device will mark it as trusted, and also mark your device as trusted to the partner.";
// MARK: Start
"device_verification_start_title" = "Verify by comparing a short text string";
"device_verification_start_wait_partner" = "Waiting for partner to accept...";
"device_verification_start_use_legacy" = "Nothing appearing? Not all clients supports interactive verification yet. Use legacy verification.";
"device_verification_start_verify_button" = "Begin Verifying";
"device_verification_start_use_legacy_action" = "Use Legacy Verification";
// MARK: Verify
"device_verification_verify_title_emoji" = "Verify this device by confirming the following emoji appear on the screen of the partner";
"device_verification_verify_title_number" = "Verify this device by confirming the following numbers appear on the screen of the partner";
"device_verification_verify_wait_partner" = "Waiting for partner to confirm...";
// MARK: Verified
"device_verification_verified_title" = "Verified!";
"device_verification_verified_description_1" = "You've successfully verified this device.";
"device_verification_verified_description_2" = "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.";
"device_verification_verified_got_it_button" = "Got it";
// MARK: Emoji
"device_verification_emoji_dog" = "Dog";
"device_verification_emoji_cat" = "Cat";
"device_verification_emoji_lion" = "Lion";
"device_verification_emoji_horse" = "Horse";
"device_verification_emoji_unicorn" = "Unicorn";
"device_verification_emoji_pig" = "Pig";
"device_verification_emoji_elephant" = "Elephant";
"device_verification_emoji_rabbit" = "Rabbit";
"device_verification_emoji_panda" = "Panda";
"device_verification_emoji_rooster" = "Rooster";
"device_verification_emoji_penguin" = "Penguin";
"device_verification_emoji_turtle" = "Turtle";
"device_verification_emoji_fish" = "Fish";
"device_verification_emoji_octopus" = "Octopus";
"device_verification_emoji_butterfly" = "Butterfly";
"device_verification_emoji_flower" = "Flower";
"device_verification_emoji_tree" = "Tree";
"device_verification_emoji_cactus" = "Cactus";
"device_verification_emoji_mushroom" = "Mushroom";
"device_verification_emoji_globe" = "Globe";
"device_verification_emoji_moon" = "Moon";
"device_verification_emoji_cloud" = "Cloud";
"device_verification_emoji_fire" = "Fire";
"device_verification_emoji_banana" = "Banana";
"device_verification_emoji_apple" = "Apple";
"device_verification_emoji_strawberry" = "Strawberry";
"device_verification_emoji_corn" = "Corn";
"device_verification_emoji_pizza" = "Pizza";
"device_verification_emoji_cake" = "Cake";
"device_verification_emoji_heart" = "Heart";
"device_verification_emoji_smiley" = "Smiley";
"device_verification_emoji_robot" = "Robot";
"device_verification_emoji_hat" = "Hat";
"device_verification_emoji_glasses" = "Glasses";
"device_verification_emoji_spanner" = "Spanner";
"device_verification_emoji_santa" = "Santa";
"device_verification_emoji_thumbs up" = "Thumbs up";
"device_verification_emoji_umbrella" = "Umbrella";
"device_verification_emoji_hourglass" = "Hourglass";
"device_verification_emoji_clock" = "Class";
"device_verification_emoji_gift" = "Gift";
"device_verification_emoji_light bulb" = "Light Bulb";
"device_verification_emoji_book" = "Book";
"device_verification_emoji_pencil" = "Pencil";
"device_verification_emoji_paperclip" = "Paperclip";
"device_verification_emoji_scissors" = "Scissors";
"device_verification_emoji_padlock" = "Padlock";
"device_verification_emoji_key" = "Key";
"device_verification_emoji_hammer" = "Hammer";
"device_verification_emoji_telephone" = "Telephone";
"device_verification_emoji_flag" = "Flag";
"device_verification_emoji_train" = "Train";
"device_verification_emoji_bicycle" = "Bicycle";
"device_verification_emoji_aeroplane" = "Aeroplane";
"device_verification_emoji_rocket" = "Rocket";
"device_verification_emoji_trophy" = "Trophy";
"device_verification_emoji_ball" = "Ball";
"device_verification_emoji_guitar" = "Guitar";
"device_verification_emoji_trumpet" = "Trumpet";
"device_verification_emoji_bell" = "Ball";
"device_verification_emoji_anchor" = "Anchor";
"device_verification_emoji_headphones" = "Headphones";
"device_verification_emoji_folder" = "Folder";
"device_verification_emoji_pin" = "Pin";
+1 -1
View File
@@ -47,6 +47,6 @@
/* Incoming named video conference invite from a specific person */
"VIDEO_CONF_NAMED_FROM_USER" = "映像つき会議通話の着信 from %@: '%@'";
/* A single unread message in a room */
"SINGLE_UNREAD_IN_ROOM" = "@にメッセージを受け取りました";
"SINGLE_UNREAD_IN_ROOM" = "%@にメッセージを受け取りました";
/* A single unread message */
"SINGLE_UNREAD" = "あなたはメッセージを受け取りました";
+10 -10
View File
@@ -246,7 +246,7 @@
// Room Preview
"room_preview_invitation_format" = "あなたは %@ さんに呼ばれてこの部屋へ参加しました";
"room_preview_subtitle" = "これは部屋の下見です。発言があっても部屋は更新されません。";
"room_preview_unlinked_email_warning" = "このアカウントに関連付けられていない @ 宛に招待が送信されました。別のアカウントでログインするか、この電子メールアドレスをこのアカウントに追加することができます。";
"room_preview_unlinked_email_warning" = "このアカウントに関連付けられていない %@ 宛に招待が送信されました。別のアカウントでログインするか、この電子メールアドレスをこのアカウントに追加することができます。";
"room_preview_try_join_an_unknown_room" = "あなたは %@ へ接続しようとしています。この会議に参加しますか?";
"room_preview_try_join_an_unknown_room_default" = "部屋";
// Settings
@@ -435,7 +435,7 @@
"no_voip_title" = "通話着信中";
"no_voip" = "%@ さんから通話の着信がありましたが、 %@ は通話をまだサポートしていません。\nこの通知を無視して、別の端末から着信に応答することも、拒否することもできます。";
// Crash report
"google_analytics_use_prompt" = "匿名の誤動作報告と使用状況データを自動的に報告して@の改善に役立てますか?";
"google_analytics_use_prompt" = "匿名の誤動作報告と使用状況データを自動的に報告して%@の改善に役立てますか?";
// Crypto
"e2e_enabling_on_app_update" = "Riotはend-to-end暗号化をサポートするようになりましたが、再度有効にするにはログインする必要があります。\n\nアプリの設定から再ログインできます。今すぐ、または後からでも構いません。";
"e2e_need_log_in_again" = "この端末のエンドツーエンド暗号鍵を生成し、接続先サーバに公開鍵を送信するには、再度ログインする必要があります。\n一度回線を切断します。ご不便おかけしてすみません。";
@@ -498,9 +498,9 @@
"settings_labs_room_members_lazy_loading_error_message" = "あなたのホームサーバーはまだルームメンバーの遅延ロードをサポートしていません。 後で試してください。";
"settings_deactivate_my_account" = "アカウントを無効にします";
"room_details_flair_section" = "コミュニティの特色を表示";
"room_details_new_flair_placeholder" = "新しいコミュニティIDを追加 (例 +foo@)";
"room_details_new_flair_placeholder" = "新しいコミュニティIDを追加 (例 +foo%@)";
"room_details_flair_invalid_id_prompt_title" = "無効な形式";
"room_details_flair_invalid_id_prompt_msg" = "@はコミュニティの有効な識別子ではありません";
"room_details_flair_invalid_id_prompt_msg" = "%@はコミュニティの有効な識別子ではありません";
"room_details_fail_to_update_room_communities" = "関連コミュニティを更新できない";
// Group Details
"group_details_title" = "コミュニティの詳細";
@@ -512,15 +512,15 @@
"group_home_multi_members_format" = "%tu メンバー";
"group_home_one_room_format" = "1 部屋";
"group_home_multi_rooms_format" = "%tu 部屋";
"group_invitation_format" = "@がこのコミュニティにあなたを招待しました";
"group_invitation_format" = "%@がこのコミュニティにあなたを招待しました";
// Group participants
"group_participants_add_participant" = "参加者を追加";
"group_participants_leave_prompt_title" = "グループを退出";
"group_participants_leave_prompt_msg" = "本当にグループを退出しますか?";
"group_participants_remove_prompt_title" = "確認";
"group_participants_remove_prompt_msg" = "本当にこのグループから@を削除しますか?";
"group_participants_remove_prompt_msg" = "本当にこのグループから%@を削除しますか?";
"group_participants_invite_prompt_title" = "確認";
"group_participants_invite_prompt_msg" = "本当にこのグループに@を招待しますか?";
"group_participants_invite_prompt_msg" = "本当にこのグループに%@を招待しますか?";
"group_participants_filter_members" = "コミュニティメンバーをフィルタリング";
"group_participants_invite_another_user" = "ユーザーIDまたは名前による検索/招待";
"group_participants_invite_malformed_id_title" = "招待エラー";
@@ -535,13 +535,13 @@
"widget_sticker_picker_no_stickerpacks_alert_add_now" = "今すぐ追加しますか?";
// Room key request dialog
"e2e_room_key_request_title" = "暗号化キー要求";
"e2e_room_key_request_message_new_device" = "暗号化キーを要求している新しい端末 '@'を追加しました。";
"e2e_room_key_request_message" = "検証されていない端末 '@'が暗号化キーを要求しています。";
"e2e_room_key_request_message_new_device" = "暗号化キーを要求している新しい端末 '%@'を追加しました。";
"e2e_room_key_request_message" = "検証されていない端末 '%@'が暗号化キーを要求しています。";
"e2e_room_key_request_start_verification" = "検証開始...";
"e2e_room_key_request_share_without_verifying" = "検証せずに共有";
"e2e_room_key_request_ignore_request" = "要求を無視";
// GDPR
"gdpr_consent_not_given_alert_message" = "@ホームサーバーを引き続き使用するには、利用規約を確認して同意する必要があります。";
"gdpr_consent_not_given_alert_message" = "%@ホームサーバーを引き続き使用するには、利用規約を確認して同意する必要があります。";
"gdpr_consent_not_given_alert_review_now_action" = "今レビュー";
"deactivate_account_title" = "無効なアカウント";
"deactivate_account_informations_part1" = "これにより、アカウントは永久に使用できなくなります。 ログインすることはできず、誰も同じユーザーIDを再登録することはできません。 これにより、あなたのアカウントは参加しているすべての部屋から退去し、あなたのIDサーバーからアカウントの詳細が削除されます。 ";
+1 -1
View File
@@ -91,7 +91,7 @@
"auth_reset_password_email_validation_message" = "Een e-mail is naar %@ gestuurd. Zodra je de link die het bevat hebt gevolgd, klik hieronder.";
"auth_reset_password_next_step_button" = "Ik heb mijn e-mailadres geverifieerd";
"auth_reset_password_error_unauthorized" = "Het is niet gelukt om het e-mailadres te verifiëren: wees er zeker van dat je op de link in de e-mail hebt geklikt";
"auth_reset_password_error_not_found" = "Het ziet er niet naar uit dat het e-mailadres met het Matrix ID op deze Homeserver is verbonden.";
"auth_reset_password_error_not_found" = "Het ziet er niet naar uit dat het e-mailadres met het Matrix ID op deze homeserver is verbonden.";
"auth_reset_password_success_message" = "Je wachtwoord is opnieuw ingesteld.\n\nJe bent op alle apparaten uitgelogd en je zal geen notificaties meer ontvangen. Om notificaties weer in te schakelen, log op elk apparaat opnieuw in.";
"auth_add_email_and_phone_warning" = "Registratie met e-mail en telefoonnummer tegelijkertijd wordt nog niet ondersteund totdat de api bestaat. Alleen het telefoonnummer zal worden gebruikt. Je kan je e-mailadres later aan je profiel in de instellingen toevoegen.";
// Chat creation
+1 -1
View File
@@ -103,7 +103,7 @@
"auth_reset_password_email_validation_message" = "An email has been sent to %@. Once you've followed the link it contains, click below.";
"auth_reset_password_next_step_button" = "I have verified my email address";
"auth_reset_password_error_unauthorized" = "Failed to verify email address: make sure you clicked the link in the email";
"auth_reset_password_error_not_found" = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
"auth_reset_password_error_not_found" = "Your email address does not appear to be associated with a Matrix ID on this homeserver.";
"auth_reset_password_success_message" = "Your password has been reset.\n\nYou have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, re-log in on each device.";
"auth_add_email_and_phone_warning" = "Registration with email and phone number at once is not supported yet until the api exists. Only the phone number will be taken into account. You may add your email to your profile in settings.";
"auth_accept_policies" = "Please review and accept the policies of this homeserver:";
+1 -1
View File
@@ -84,7 +84,7 @@
"auth_reset_password_email_validation_message" = "Email đã được gửi tới %@. Khi bạn đã theo liên kết trong đó, bấm vào dưới đây.";
"auth_reset_password_next_step_button" = "Tôi đã xác thực địa chỉ email của tôi";
"auth_reset_password_error_unauthorized" = "Xác thực địa chỉ email thất bại: hãy đảm bảo rằng bạn đã bấm vào địa chỉ đính kèm trong email";
"auth_reset_password_error_not_found" = "Địa chỉ email có vẻ chưa được liên kết với Matrix ID trên Homeserver này.";
"auth_reset_password_error_not_found" = "Địa chỉ email có vẻ chưa được liên kết với Matrix ID trên homeserver này.";
"auth_reset_password_success_message" = "Mật khẩu của bạn đã được thiết lập lại.\n\nBạn đã được đăng xuất khỏi tất cả các thiết bị và sẽ không còn nhận được thông báo. Để bật lại thông báo, đăng nhập lại trên mỗi thiết bị.";
"auth_add_email_and_phone_warning" = "Đăng kí với mật khẩu và số điện thoại cùng lúc chưa được hỗ trợ cho tới khi api được thiết lập. Duy nhất số điện thoại sẽ được liên kết với với tài khoản. Bạn sẽ phải thêm email vào hồ sơ trong mục cài đặt.";
// Chat creation
+1 -1
View File
@@ -577,7 +577,7 @@
// Share extension
"share_extension_auth_prompt" = "登录主应用程序以共享内容";
"share_extension_failed_to_encrypt" = "发送失败。 请在主应用程序中检查此房间的加密设置";
"e2e_room_key_request_message_new_device" = "您添加了一个新设备 '@' ,它正在请求加密密钥。";
"e2e_room_key_request_message_new_device" = "您添加了一个新设备 '%@' ,它正在请求加密密钥。";
"e2e_room_key_request_message" = "您的未验证设备 '%@' 正在请求加密密钥。";
// GDPR
"gdpr_consent_not_given_alert_message" = "要继续使用该 %@ 主服务器,您必须查看并同意其服务条款和条件。";
+3 -3
View File
@@ -213,9 +213,9 @@
"search_in_progress" = "搜尋中……";
// Directory
"directory_cell_title" = "瀏覽目錄";
"directory_cell_description" = "tu 個聊天室";
"directory_cell_description" = "%tu 個聊天室";
"directory_search_results_title" = "聊天室目錄搜尋結果";
"directory_search_results" = "搜尋 %@ 有 tu 個結果";
"directory_search_results" = "搜尋 %@ 有 %tu 個結果";
"directory_search_results_more_than" = "搜尋 %@ 有超過 %tu 個結果";
"directory_searching_title" = "搜尋聊天室目錄中……";
"directory_search_fail" = "無法取得資料";
@@ -437,7 +437,7 @@
"group_home_one_member_format" = "1 位成員";
"group_home_multi_members_format" = "%tu 位成員";
"group_home_one_room_format" = "一個聊天室";
"group_home_multi_rooms_format" = "tu 個聊天室";
"group_home_multi_rooms_format" = "%tu 個聊天室";
"group_invitation_format" = "%@ 邀請您加入此社群";
// Group participants
"group_participants_add_participant" = "新增成員";
+1
View File
@@ -22,6 +22,7 @@ internal enum RiotDefaults {
internal static let integrationsRestUrl: String = _document["integrationsRestUrl"]
internal static let integrationsUiUrl: String = _document["integrationsUiUrl"]
internal static let integrationsWidgetsUrls: [String] = _document["integrationsWidgetsUrls"]
internal static let jitsiServerURL: String = _document["jitsiServerURL"]
internal static let matrixApps: Bool = _document["matrixApps"]
internal static let maxAllowedMediaCacheSize: Int = _document["maxAllowedMediaCacheSize"]
internal static let pinRoomsWithMissedNotif: Bool = _document["pinRoomsWithMissedNotif"]
+35
View File
@@ -12,6 +12,31 @@ import UIKit
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
internal enum StoryboardScene {
internal enum DeviceVerificationDataLoadingViewController: StoryboardType {
internal static let storyboardName = "DeviceVerificationDataLoadingViewController"
internal static let initialScene = InitialSceneType<Riot.DeviceVerificationDataLoadingViewController>(storyboard: DeviceVerificationDataLoadingViewController.self)
}
internal enum DeviceVerificationIncomingViewController: StoryboardType {
internal static let storyboardName = "DeviceVerificationIncomingViewController"
internal static let initialScene = InitialSceneType<Riot.DeviceVerificationIncomingViewController>(storyboard: DeviceVerificationIncomingViewController.self)
}
internal enum DeviceVerificationStartViewController: StoryboardType {
internal static let storyboardName = "DeviceVerificationStartViewController"
internal static let initialScene = InitialSceneType<Riot.DeviceVerificationStartViewController>(storyboard: DeviceVerificationStartViewController.self)
}
internal enum DeviceVerificationVerifiedViewController: StoryboardType {
internal static let storyboardName = "DeviceVerificationVerifiedViewController"
internal static let initialScene = InitialSceneType<Riot.DeviceVerificationVerifiedViewController>(storyboard: DeviceVerificationVerifiedViewController.self)
}
internal enum DeviceVerificationVerifyViewController: StoryboardType {
internal static let storyboardName = "DeviceVerificationVerifyViewController"
internal static let initialScene = InitialSceneType<Riot.DeviceVerificationVerifyViewController>(storyboard: DeviceVerificationVerifyViewController.self)
}
internal enum KeyBackupRecoverFromPassphraseViewController: StoryboardType {
internal static let storyboardName = "KeyBackupRecoverFromPassphraseViewController"
@@ -47,6 +72,16 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.KeyBackupSetupSuccessFromRecoveryKeyViewController>(storyboard: KeyBackupSetupSuccessFromRecoveryKeyViewController.self)
}
internal enum SimpleScreenTemplateViewController: StoryboardType {
internal static let storyboardName = "SimpleScreenTemplateViewController"
internal static let initialScene = InitialSceneType<Riot.SimpleScreenTemplateViewController>(storyboard: SimpleScreenTemplateViewController.self)
}
internal enum TemplateScreenViewController: StoryboardType {
internal static let storyboardName = "TemplateScreenViewController"
internal static let initialScene = InitialSceneType<Riot.TemplateScreenViewController>(storyboard: TemplateScreenViewController.self)
}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
+355 -19
View File
@@ -38,7 +38,7 @@ internal enum VectorL10n {
internal static var authAddEmailAndPhoneWarning: String {
return VectorL10n.tr("Vector", "auth_add_email_and_phone_warning")
}
/// Add an email address to your account to let users discover you, and let you reset password.
/// Add an email address to your account to let users discover you, and to reset your password.
internal static var authAddEmailMessage: String {
return VectorL10n.tr("Vector", "auth_add_email_message")
}
@@ -166,7 +166,7 @@ internal enum VectorL10n {
internal static var authPhonePlaceholder: String {
return VectorL10n.tr("Vector", "auth_phone_placeholder")
}
/// This Home Server would like to make sure you are not a robot
/// This homeserver would like to make sure you are not a robot
internal static var authRecaptchaMessage: String {
return VectorL10n.tr("Vector", "auth_recaptcha_message")
}
@@ -186,7 +186,7 @@ internal enum VectorL10n {
internal static func authResetPasswordEmailValidationMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "auth_reset_password_email_validation_message", p1)
}
/// Your email address does not appear to be associated with a Matrix ID on this Homeserver.
/// Your email address does not appear to be associated with a Matrix ID on this homeserver.
internal static var authResetPasswordErrorNotFound: String {
return VectorL10n.tr("Vector", "auth_reset_password_error_not_found")
}
@@ -426,6 +426,342 @@ internal enum VectorL10n {
internal static var decline: String {
return VectorL10n.tr("Vector", "decline")
}
/// The other party cancelled the verification.
internal static var deviceVerificationCancelled: String {
return VectorL10n.tr("Vector", "device_verification_cancelled")
}
/// The verification has been cancelled. Reason: %@
internal static func deviceVerificationCancelledByMe(_ p1: String) -> String {
return VectorL10n.tr("Vector", "device_verification_cancelled_by_me", p1)
}
/// Aeroplane
internal static var deviceVerificationEmojiAeroplane: String {
return VectorL10n.tr("Vector", "device_verification_emoji_aeroplane")
}
/// Anchor
internal static var deviceVerificationEmojiAnchor: String {
return VectorL10n.tr("Vector", "device_verification_emoji_anchor")
}
/// Apple
internal static var deviceVerificationEmojiApple: String {
return VectorL10n.tr("Vector", "device_verification_emoji_apple")
}
/// Ball
internal static var deviceVerificationEmojiBall: String {
return VectorL10n.tr("Vector", "device_verification_emoji_ball")
}
/// Banana
internal static var deviceVerificationEmojiBanana: String {
return VectorL10n.tr("Vector", "device_verification_emoji_banana")
}
/// Ball
internal static var deviceVerificationEmojiBell: String {
return VectorL10n.tr("Vector", "device_verification_emoji_bell")
}
/// Bicycle
internal static var deviceVerificationEmojiBicycle: String {
return VectorL10n.tr("Vector", "device_verification_emoji_bicycle")
}
/// Book
internal static var deviceVerificationEmojiBook: String {
return VectorL10n.tr("Vector", "device_verification_emoji_book")
}
/// Butterfly
internal static var deviceVerificationEmojiButterfly: String {
return VectorL10n.tr("Vector", "device_verification_emoji_butterfly")
}
/// Cactus
internal static var deviceVerificationEmojiCactus: String {
return VectorL10n.tr("Vector", "device_verification_emoji_cactus")
}
/// Cake
internal static var deviceVerificationEmojiCake: String {
return VectorL10n.tr("Vector", "device_verification_emoji_cake")
}
/// Cat
internal static var deviceVerificationEmojiCat: String {
return VectorL10n.tr("Vector", "device_verification_emoji_cat")
}
/// Class
internal static var deviceVerificationEmojiClock: String {
return VectorL10n.tr("Vector", "device_verification_emoji_clock")
}
/// Cloud
internal static var deviceVerificationEmojiCloud: String {
return VectorL10n.tr("Vector", "device_verification_emoji_cloud")
}
/// Corn
internal static var deviceVerificationEmojiCorn: String {
return VectorL10n.tr("Vector", "device_verification_emoji_corn")
}
/// Dog
internal static var deviceVerificationEmojiDog: String {
return VectorL10n.tr("Vector", "device_verification_emoji_dog")
}
/// Elephant
internal static var deviceVerificationEmojiElephant: String {
return VectorL10n.tr("Vector", "device_verification_emoji_elephant")
}
/// Fire
internal static var deviceVerificationEmojiFire: String {
return VectorL10n.tr("Vector", "device_verification_emoji_fire")
}
/// Fish
internal static var deviceVerificationEmojiFish: String {
return VectorL10n.tr("Vector", "device_verification_emoji_fish")
}
/// Flag
internal static var deviceVerificationEmojiFlag: String {
return VectorL10n.tr("Vector", "device_verification_emoji_flag")
}
/// Flower
internal static var deviceVerificationEmojiFlower: String {
return VectorL10n.tr("Vector", "device_verification_emoji_flower")
}
/// Folder
internal static var deviceVerificationEmojiFolder: String {
return VectorL10n.tr("Vector", "device_verification_emoji_folder")
}
/// Gift
internal static var deviceVerificationEmojiGift: String {
return VectorL10n.tr("Vector", "device_verification_emoji_gift")
}
/// Glasses
internal static var deviceVerificationEmojiGlasses: String {
return VectorL10n.tr("Vector", "device_verification_emoji_glasses")
}
/// Globe
internal static var deviceVerificationEmojiGlobe: String {
return VectorL10n.tr("Vector", "device_verification_emoji_globe")
}
/// Guitar
internal static var deviceVerificationEmojiGuitar: String {
return VectorL10n.tr("Vector", "device_verification_emoji_guitar")
}
/// Hammer
internal static var deviceVerificationEmojiHammer: String {
return VectorL10n.tr("Vector", "device_verification_emoji_hammer")
}
/// Hat
internal static var deviceVerificationEmojiHat: String {
return VectorL10n.tr("Vector", "device_verification_emoji_hat")
}
/// Headphones
internal static var deviceVerificationEmojiHeadphones: String {
return VectorL10n.tr("Vector", "device_verification_emoji_headphones")
}
/// Heart
internal static var deviceVerificationEmojiHeart: String {
return VectorL10n.tr("Vector", "device_verification_emoji_heart")
}
/// Horse
internal static var deviceVerificationEmojiHorse: String {
return VectorL10n.tr("Vector", "device_verification_emoji_horse")
}
/// Hourglass
internal static var deviceVerificationEmojiHourglass: String {
return VectorL10n.tr("Vector", "device_verification_emoji_hourglass")
}
/// Key
internal static var deviceVerificationEmojiKey: String {
return VectorL10n.tr("Vector", "device_verification_emoji_key")
}
/// Light Bulb
internal static var deviceVerificationEmojiLightBulb: String {
return VectorL10n.tr("Vector", "device_verification_emoji_light bulb")
}
/// Lion
internal static var deviceVerificationEmojiLion: String {
return VectorL10n.tr("Vector", "device_verification_emoji_lion")
}
/// Moon
internal static var deviceVerificationEmojiMoon: String {
return VectorL10n.tr("Vector", "device_verification_emoji_moon")
}
/// Mushroom
internal static var deviceVerificationEmojiMushroom: String {
return VectorL10n.tr("Vector", "device_verification_emoji_mushroom")
}
/// Octopus
internal static var deviceVerificationEmojiOctopus: String {
return VectorL10n.tr("Vector", "device_verification_emoji_octopus")
}
/// Padlock
internal static var deviceVerificationEmojiPadlock: String {
return VectorL10n.tr("Vector", "device_verification_emoji_padlock")
}
/// Panda
internal static var deviceVerificationEmojiPanda: String {
return VectorL10n.tr("Vector", "device_verification_emoji_panda")
}
/// Paperclip
internal static var deviceVerificationEmojiPaperclip: String {
return VectorL10n.tr("Vector", "device_verification_emoji_paperclip")
}
/// Pencil
internal static var deviceVerificationEmojiPencil: String {
return VectorL10n.tr("Vector", "device_verification_emoji_pencil")
}
/// Penguin
internal static var deviceVerificationEmojiPenguin: String {
return VectorL10n.tr("Vector", "device_verification_emoji_penguin")
}
/// Pig
internal static var deviceVerificationEmojiPig: String {
return VectorL10n.tr("Vector", "device_verification_emoji_pig")
}
/// Pin
internal static var deviceVerificationEmojiPin: String {
return VectorL10n.tr("Vector", "device_verification_emoji_pin")
}
/// Pizza
internal static var deviceVerificationEmojiPizza: String {
return VectorL10n.tr("Vector", "device_verification_emoji_pizza")
}
/// Rabbit
internal static var deviceVerificationEmojiRabbit: String {
return VectorL10n.tr("Vector", "device_verification_emoji_rabbit")
}
/// Robot
internal static var deviceVerificationEmojiRobot: String {
return VectorL10n.tr("Vector", "device_verification_emoji_robot")
}
/// Rocket
internal static var deviceVerificationEmojiRocket: String {
return VectorL10n.tr("Vector", "device_verification_emoji_rocket")
}
/// Rooster
internal static var deviceVerificationEmojiRooster: String {
return VectorL10n.tr("Vector", "device_verification_emoji_rooster")
}
/// Santa
internal static var deviceVerificationEmojiSanta: String {
return VectorL10n.tr("Vector", "device_verification_emoji_santa")
}
/// Scissors
internal static var deviceVerificationEmojiScissors: String {
return VectorL10n.tr("Vector", "device_verification_emoji_scissors")
}
/// Smiley
internal static var deviceVerificationEmojiSmiley: String {
return VectorL10n.tr("Vector", "device_verification_emoji_smiley")
}
/// Spanner
internal static var deviceVerificationEmojiSpanner: String {
return VectorL10n.tr("Vector", "device_verification_emoji_spanner")
}
/// Strawberry
internal static var deviceVerificationEmojiStrawberry: String {
return VectorL10n.tr("Vector", "device_verification_emoji_strawberry")
}
/// Telephone
internal static var deviceVerificationEmojiTelephone: String {
return VectorL10n.tr("Vector", "device_verification_emoji_telephone")
}
/// Thumbs up
internal static var deviceVerificationEmojiThumbsUp: String {
return VectorL10n.tr("Vector", "device_verification_emoji_thumbs up")
}
/// Train
internal static var deviceVerificationEmojiTrain: String {
return VectorL10n.tr("Vector", "device_verification_emoji_train")
}
/// Tree
internal static var deviceVerificationEmojiTree: String {
return VectorL10n.tr("Vector", "device_verification_emoji_tree")
}
/// Trophy
internal static var deviceVerificationEmojiTrophy: String {
return VectorL10n.tr("Vector", "device_verification_emoji_trophy")
}
/// Trumpet
internal static var deviceVerificationEmojiTrumpet: String {
return VectorL10n.tr("Vector", "device_verification_emoji_trumpet")
}
/// Turtle
internal static var deviceVerificationEmojiTurtle: String {
return VectorL10n.tr("Vector", "device_verification_emoji_turtle")
}
/// Umbrella
internal static var deviceVerificationEmojiUmbrella: String {
return VectorL10n.tr("Vector", "device_verification_emoji_umbrella")
}
/// Unicorn
internal static var deviceVerificationEmojiUnicorn: String {
return VectorL10n.tr("Vector", "device_verification_emoji_unicorn")
}
/// Cannot load device information.
internal static var deviceVerificationErrorCannotLoadDevice: String {
return VectorL10n.tr("Vector", "device_verification_error_cannot_load_device")
}
/// Verify this device to mark it as trusted. Trusting devices of partners gives you extra peace of mind when using end-to-end encrypted messages.
internal static var deviceVerificationIncomingDescription1: String {
return VectorL10n.tr("Vector", "device_verification_incoming_description_1")
}
/// Verifying this device will mark it as trusted, and also mark your device as trusted to the partner.
internal static var deviceVerificationIncomingDescription2: String {
return VectorL10n.tr("Vector", "device_verification_incoming_description_2")
}
/// Incoming Verification Request
internal static var deviceVerificationIncomingTitle: String {
return VectorL10n.tr("Vector", "device_verification_incoming_title")
}
/// For maximum security, we recommend you do this in person or use another trusted means of communication
internal static var deviceVerificationSecurityAdvice: String {
return VectorL10n.tr("Vector", "device_verification_security_advice")
}
/// Verify by comparing a short text string
internal static var deviceVerificationStartTitle: String {
return VectorL10n.tr("Vector", "device_verification_start_title")
}
/// Nothing appearing? Not all clients supports interactive verification yet. Use legacy verification.
internal static var deviceVerificationStartUseLegacy: String {
return VectorL10n.tr("Vector", "device_verification_start_use_legacy")
}
/// Use Legacy Verification
internal static var deviceVerificationStartUseLegacyAction: String {
return VectorL10n.tr("Vector", "device_verification_start_use_legacy_action")
}
/// Begin Verifying
internal static var deviceVerificationStartVerifyButton: String {
return VectorL10n.tr("Vector", "device_verification_start_verify_button")
}
/// Waiting for partner to accept...
internal static var deviceVerificationStartWaitPartner: String {
return VectorL10n.tr("Vector", "device_verification_start_wait_partner")
}
/// Verify device
internal static var deviceVerificationTitle: String {
return VectorL10n.tr("Vector", "device_verification_title")
}
/// You've successfully verified this device.
internal static var deviceVerificationVerifiedDescription1: String {
return VectorL10n.tr("Vector", "device_verification_verified_description_1")
}
/// Secure messages with this user are end-to-end encrypted and not able to be read by third parties.
internal static var deviceVerificationVerifiedDescription2: String {
return VectorL10n.tr("Vector", "device_verification_verified_description_2")
}
/// Got it
internal static var deviceVerificationVerifiedGotItButton: String {
return VectorL10n.tr("Vector", "device_verification_verified_got_it_button")
}
/// Verified!
internal static var deviceVerificationVerifiedTitle: String {
return VectorL10n.tr("Vector", "device_verification_verified_title")
}
/// Verify this device by confirming the following emoji appear on the screen of the partner
internal static var deviceVerificationVerifyTitleEmoji: String {
return VectorL10n.tr("Vector", "device_verification_verify_title_emoji")
}
/// Verify this device by confirming the following numbers appear on the screen of the partner
internal static var deviceVerificationVerifyTitleNumber: String {
return VectorL10n.tr("Vector", "device_verification_verify_title_number")
}
/// Waiting for partner to confirm...
internal static var deviceVerificationVerifyWaitPartner: String {
return VectorL10n.tr("Vector", "device_verification_verify_wait_partner")
}
/// %tu rooms
internal static func directoryCellDescription(_ p1: Int) -> String {
return VectorL10n.tr("Vector", "directory_cell_description", p1)
@@ -686,14 +1022,14 @@ internal enum VectorL10n {
internal static var join: String {
return VectorL10n.tr("Vector", "join")
}
/// Use Key Backup
internal static var keyBackupRecoverBannerSubtitle: String {
return VectorL10n.tr("Vector", "key_backup_recover_banner_subtitle")
}
/// Never lose encrypted messages
internal static var keyBackupRecoverBannerTitle: String {
return VectorL10n.tr("Vector", "key_backup_recover_banner_title")
}
/// Connect this device to Key Backup
internal static var keyBackupRecoverConnentBannerSubtitle: String {
return VectorL10n.tr("Vector", "key_backup_recover_connent_banner_subtitle")
}
/// Done
internal static var keyBackupRecoverDoneAction: String {
return VectorL10n.tr("Vector", "key_backup_recover_done_action")
@@ -790,14 +1126,14 @@ internal enum VectorL10n {
internal static var keyBackupSetupIntroManualExportInfo: String {
return VectorL10n.tr("Vector", "key_backup_setup_intro_manual_export_info")
}
/// Use Key Backup
internal static var keyBackupSetupIntroSetupActionWithExistingBackup: String {
return VectorL10n.tr("Vector", "key_backup_setup_intro_setup_action_with_existing_backup")
}
/// Start using Key Backup
internal static var keyBackupSetupIntroSetupActionWithoutExistingBackup: String {
return VectorL10n.tr("Vector", "key_backup_setup_intro_setup_action_without_existing_backup")
}
/// Connect this device to Key Backup
internal static var keyBackupSetupIntroSetupConnectActionWithExistingBackup: String {
return VectorL10n.tr("Vector", "key_backup_setup_intro_setup_connect_action_with_existing_backup")
}
/// Never lose encrypted messages
internal static var keyBackupSetupIntroTitle: String {
return VectorL10n.tr("Vector", "key_backup_setup_intro_title")
@@ -1878,7 +2214,7 @@ internal enum VectorL10n {
internal static var settingsClearCache: String {
return VectorL10n.tr("Vector", "settings_clear_cache")
}
/// Home server is %@
/// Homeserver is %@
internal static func settingsConfigHomeServer(_ p1: String) -> String {
return VectorL10n.tr("Vector", "settings_config_home_server", p1)
}
@@ -2006,6 +2342,10 @@ internal enum VectorL10n {
internal static var settingsKeyBackup: String {
return VectorL10n.tr("Vector", "settings_key_backup")
}
/// Connect this device to Key Backup
internal static var settingsKeyBackupButtonConnect: String {
return VectorL10n.tr("Vector", "settings_key_backup_button_connect")
}
/// Start using Key Backup
internal static var settingsKeyBackupButtonCreate: String {
return VectorL10n.tr("Vector", "settings_key_backup_button_create")
@@ -2018,10 +2358,6 @@ internal enum VectorL10n {
internal static var settingsKeyBackupButtonRestore: String {
return VectorL10n.tr("Vector", "settings_key_backup_button_restore")
}
/// Use key backup
internal static var settingsKeyBackupButtonUse: String {
return VectorL10n.tr("Vector", "settings_key_backup_button_use")
}
/// Are you sure? You will lose your encrypted messages if your keys are not backed up properly.
internal static var settingsKeyBackupDeleteConfirmationPromptMsg: String {
return VectorL10n.tr("Vector", "settings_key_backup_delete_confirmation_prompt_msg")
@@ -2046,7 +2382,7 @@ internal enum VectorL10n {
internal static var settingsKeyBackupInfoNone: String {
return VectorL10n.tr("Vector", "settings_key_backup_info_none")
}
/// This device is not backing up your keys.
/// This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.
internal static var settingsKeyBackupInfoNotValid: String {
return VectorL10n.tr("Vector", "settings_key_backup_info_not_valid")
}
@@ -2058,7 +2394,7 @@ internal enum VectorL10n {
internal static var settingsKeyBackupInfoProgressDone: String {
return VectorL10n.tr("Vector", "settings_key_backup_info_progress_done")
}
/// Back up your keys before signing out to avoid losing them.
/// Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.
internal static var settingsKeyBackupInfoSignoutWarning: String {
return VectorL10n.tr("Vector", "settings_key_backup_info_signout_warning")
}
@@ -2488,7 +2824,7 @@ internal enum VectorL10n {
// MARK: - Implementation Details
extension VectorL10n {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "")
let locale: Locale
if let localeIdentifier = Bundle.mxk_language() {
+21
View File
@@ -0,0 +1,21 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol Themable: class {
func update(theme: Theme)
}
@@ -102,7 +102,7 @@
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="AuthenticationVCActivityIndicator"/>
</userDefinedRuntimeAttributes>
</activityIndicatorView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Currently we do not support authentication flows defined by this Home Server" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="54b-4O-ip9" userLabel="noFlowLabel">
<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"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -226,7 +226,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1YY-gb-LG4">
<rect key="frame" x="0.0" y="0.0" width="375" height="70"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Home Server:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kHf-s1-cZa">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Homeserver:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kHf-s1-cZa">
<rect key="frame" x="18" y="8" width="339" height="20"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCHSLabel"/>
<constraints>
@@ -848,7 +848,7 @@
if (restClient)
{
// Sanity check on home server
// Sanity check on homeserver
id hs_url = registrationParameters[@"hs_url"];
if (hs_url && [hs_url isKindOfClass:NSString.class])
{
@@ -759,8 +759,8 @@
{
// Avoid multiple openings of rooms
self.userInteractionEnabled = NO;
[[AppDelegate theDelegate].masterTabBarController selectRoomWithId:roomId andEventId:nil inMatrixSession:matrixSession completion:^{
[[AppDelegate theDelegate] showRoom:roomId andEventId:nil withMatrixSession:matrixSession restoreInitialDisplay:NO completion:^{
self.userInteractionEnabled = YES;
}];
}
@@ -125,28 +125,14 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
}
// Use bold font for the room title
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
{
self.roomTitle.font = [UIFont systemFontOfSize:17 weight:UIFontWeightBold];
}
else
{
self.roomTitle.font = [UIFont boldSystemFontOfSize:17];
}
self.roomTitle.font = [UIFont systemFontOfSize:17 weight:UIFontWeightBold];
}
else
{
self.lastEventDate.textColor = ThemeService.shared.theme.textSecondaryColor;
// The room title is not bold anymore
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
{
self.roomTitle.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium];
}
else
{
self.roomTitle.font = [UIFont systemFontOfSize:17];
}
// The room title is not bold anymore
self.roomTitle.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium];
}
self.directRoomBorderView.hidden = !roomCellData.roomSummary.room.isDirect;
@@ -859,7 +859,7 @@
}
} failure:^(NSError *error) {
NSLog(@"[GroupHomeViewController] Error: The home server failed to resolve the room alias (%@)", roomIdOrAlias);
NSLog(@"[GroupHomeViewController] Error: The homeserver failed to resolve the room alias (%@)", roomIdOrAlias);
}];
}
}
@@ -0,0 +1,186 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
@objcMembers
final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
// MARK: - Properties
// MARK: Private
private let navigationRouter: NavigationRouterType
private let session: MXSession
private let otherUserId: String
private let otherDeviceId: String
private var incomingTransaction: MXIncomingSASTransaction?
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: DeviceVerificationCoordinatorDelegate?
// MARK: - Setup
/// Contrustor to start a verification of another device.
///
/// - Parameters:
/// - session: the MXSession
/// - otherUserId: the device user id
/// - otherDevice: the device id
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
self.session = session
self.otherUserId = otherUserId
self.otherDeviceId = otherDeviceId
}
/// Contrustor to manage an incoming SAS device verification transaction
///
/// - Parameters:
/// - session: the MXSession
/// - transaction: an existing device verification transaction
convenience init(session: MXSession, incomingTransaction: MXIncomingSASTransaction) {
self.init(session: session,
otherUserId: incomingTransaction.otherUserId,
otherDeviceId: incomingTransaction.otherDeviceId)
self.incomingTransaction = incomingTransaction
}
// MARK: - Public methods
func start() {
let rootCoordinator = self.createDataLoadingScreenCoordinator()
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
self.navigationRouter.setRootModule(rootCoordinator)
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
}
// MARK: - Private methods
private func createDataLoadingScreenCoordinator() -> DeviceVerificationDataLoadingCoordinator {
let coordinator = DeviceVerificationDataLoadingCoordinator(session: self.session, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
coordinator.delegate = self
coordinator.start()
return coordinator
}
private func showStart(otherUser: MXUser, otherDevice: MXDeviceInfo) {
let coordinator = DeviceVerificationStartCoordinator(session: self.session, otherUser: otherUser, otherDevice: otherDevice)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.setRootModule(coordinator)
}
private func showIncoming(otherUser: MXUser, transaction: MXIncomingSASTransaction) {
let coordinator = DeviceVerificationIncomingCoordinator(session: self.session, otherUser: otherUser, transaction: transaction)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.setRootModule(coordinator)
}
private func showVerify(transaction: MXSASTransaction, animated: Bool) {
let coordinator = DeviceVerificationVerifyCoordinator(session: self.session, transaction: transaction)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)
self.navigationRouter.push(coordinator, animated: animated) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
private func showVerified(animated: Bool) {
let viewController = DeviceVerificationVerifiedViewController.instantiate()
viewController.delegate = self
self.navigationRouter.setRootModule(viewController)
}
}
extension DeviceVerificationCoordinator: DeviceVerificationDataLoadingCoordinatorDelegate {
func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) {
if let incomingTransaction = self.incomingTransaction {
self.showIncoming(otherUser: user, transaction: incomingTransaction)
} else {
self.showStart(otherUser: user, otherDevice: device)
}
}
func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension DeviceVerificationCoordinator: DeviceVerificationStartCoordinatorDelegate {
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) {
self.showVerify(transaction: transaction, animated: true)
}
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension DeviceVerificationCoordinator: DeviceVerificationIncomingCoordinatorDelegate {
func deviceVerificationIncomingCoordinator(_ coordinator: DeviceVerificationIncomingCoordinatorType, didAcceptTransaction transaction: MXSASTransaction) {
self.showVerify(transaction: transaction, animated: true)
}
func deviceVerificationIncomingCoordinatorDidCancel(_ coordinator: DeviceVerificationIncomingCoordinatorType) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension DeviceVerificationCoordinator: DeviceVerificationVerifyCoordinatorDelegate {
func deviceVerificationVerifyCoordinatorDidComplete(_ coordinator: DeviceVerificationVerifyCoordinatorType) {
self.showVerified(animated: true)
}
func deviceVerificationVerifyCoordinatorDidCancel(_ coordinator: DeviceVerificationVerifyCoordinatorType) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension DeviceVerificationCoordinator: DeviceVerificationVerifiedViewControllerDelegate {
func deviceVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: DeviceVerificationVerifiedViewController) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func deviceVerificationVerifiedViewControllerDidCancel(_ viewController: DeviceVerificationVerifiedViewController) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
@@ -0,0 +1,92 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
@objc protocol DeviceVerificationCoordinatorBridgePresenterDelegate {
func deviceVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: DeviceVerificationCoordinatorBridgePresenter, otherUserId: String, otherDeviceId: String)
}
/// DeviceVerificationCoordinatorBridgePresenter enables to start DeviceVerificationCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
@objcMembers
final class DeviceVerificationCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var coordinator: DeviceVerificationCoordinator?
// MARK: Public
weak var delegate: DeviceVerificationCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(session: MXSession) {
self.session = session
super.init()
}
// MARK: - Public
// NOTE: Default value feature is not compatible with Objective-C.
// func present(from viewController: UIViewController, animated: Bool) {
// self.present(from: viewController, animated: animated)
// }
func present(from viewController: UIViewController, otherUserId: String, otherDeviceId: String, animated: Bool) {
let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
deviceVerificationCoordinator.delegate = self
viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
deviceVerificationCoordinator.start()
self.coordinator = deviceVerificationCoordinator
}
func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) {
let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, incomingTransaction: incomingTransaction)
deviceVerificationCoordinator.delegate = self
viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
deviceVerificationCoordinator.start()
self.coordinator = deviceVerificationCoordinator
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
if let completion = completion {
completion()
}
}
}
}
// MARK: - DeviceVerificationCoordinatorDelegate
extension DeviceVerificationCoordinatorBridgePresenter: DeviceVerificationCoordinatorDelegate {
func deviceVerificationCoordinatorDidComplete(_ coordinator: DeviceVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) {
self.delegate?.deviceVerificationCoordinatorBridgePresenterDelegateDidComplete(self, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
}
}
@@ -0,0 +1,28 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationCoordinatorDelegate: class {
func deviceVerificationCoordinatorDidComplete(_ coordinator: DeviceVerificationCoordinatorType, otherUserId: String, otherDeviceId: String)
}
/// `DeviceVerificationCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow.
protocol DeviceVerificationCoordinatorType: Coordinator, Presentable {
var delegate: DeviceVerificationCoordinatorDelegate? { get }
}
@@ -0,0 +1,71 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class DeviceVerificationIncomingCoordinator: DeviceVerificationIncomingCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var deviceVerificationIncomingViewModel: DeviceVerificationIncomingViewModelType
private let deviceVerificationIncomingViewController: DeviceVerificationIncomingViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: DeviceVerificationIncomingCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUser: MXUser, transaction: MXIncomingSASTransaction) {
self.session = session
let deviceVerificationIncomingViewModel = DeviceVerificationIncomingViewModel(session: self.session, otherUser: otherUser, transaction: transaction)
let deviceVerificationIncomingViewController = DeviceVerificationIncomingViewController.instantiate(with: deviceVerificationIncomingViewModel)
self.deviceVerificationIncomingViewModel = deviceVerificationIncomingViewModel
self.deviceVerificationIncomingViewController = deviceVerificationIncomingViewController
}
// MARK: - Public methods
func start() {
self.deviceVerificationIncomingViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.deviceVerificationIncomingViewController
}
}
// MARK: - DeviceVerificationIncomingViewModelCoordinatorDelegate
extension DeviceVerificationIncomingCoordinator: DeviceVerificationIncomingViewModelCoordinatorDelegate {
func deviceVerificationIncomingViewModel(_ viewModel: DeviceVerificationIncomingViewModelType, didAcceptTransaction transaction: MXSASTransaction) {
self.delegate?.deviceVerificationIncomingCoordinator(self, didAcceptTransaction: transaction)
}
func deviceVerificationIncomingViewModelDidCancel(_ viewModel: DeviceVerificationIncomingViewModelType) {
self.delegate?.deviceVerificationIncomingCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationIncomingCoordinatorDelegate: class {
func deviceVerificationIncomingCoordinator(_ coordinator: DeviceVerificationIncomingCoordinatorType, didAcceptTransaction message: MXSASTransaction)
func deviceVerificationIncomingCoordinatorDidCancel(_ coordinator: DeviceVerificationIncomingCoordinatorType)
}
/// `DeviceVerificationIncomingCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol DeviceVerificationIncomingCoordinatorType: Coordinator, Presentable {
var delegate: DeviceVerificationIncomingCoordinatorDelegate? { get }
}
@@ -0,0 +1,25 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationIncomingViewController view actions exposed to view model
enum DeviceVerificationIncomingViewAction {
case accept
case cancel
}
@@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Device Verification Incoming View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="DeviceVerificationIncomingViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="375" height="528"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="375" height="528"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Incoming Verification Request" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="he8-pl-xE9">
<rect key="frame" x="20" y="35" width="335" height="41"/>
<constraints>
<constraint firstAttribute="height" constant="41" id="Nam-ca-50k"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="favourite" translatesAutoresizingMaskIntoConstraints="NO" id="4aN-Cq-vqG" customClass="MXKImageView">
<rect key="frame" x="155.5" y="86" width="64" height="64"/>
<constraints>
<constraint firstAttribute="width" constant="64" id="BSb-a6-GeY"/>
<constraint firstAttribute="height" constant="64" id="gML-vd-Y0C"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="User Displayname" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZIv-LS-3Mg">
<rect key="frame" x="20" y="158" width="335" height="21"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="DEVICEID" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MpQ-jR-g1m">
<rect key="frame" x="20" y="187" width="335" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verifying this device will mark it as trusted, and also mark your device as trusted to the partner." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6yX-xD-4X5">
<rect key="frame" x="20" y="228" width="335" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="pta-eP-0yH"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="20" y="300" width="335" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="Y2u-fJ-idS"/>
</constraints>
<string key="text">Verify this device to mark it as trusted. Trusting devices of partners gives you extra peace of mind when using end-to-end encrypted messages.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DtR-jx-UKY">
<rect key="frame" x="0.0" y="400" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="20" y="10" width="335" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Continue">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="continueButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="cMi-C9-iIh"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerY" secondItem="DtR-jx-UKY" secondAttribute="centerY" id="5eX-a5-zpP"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerX" secondItem="DtR-jx-UKY" secondAttribute="centerX" id="6v9-MN-mk2"/>
<constraint firstAttribute="trailing" secondItem="DOt-5E-FjF" secondAttribute="trailing" constant="20" id="7Cr-6b-kGn"/>
<constraint firstAttribute="height" constant="50" id="QNq-au-ZdL"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="leading" secondItem="DtR-jx-UKY" secondAttribute="leading" constant="20" id="YrM-nf-AuH"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="6yX-xD-4X5" firstAttribute="bottom" secondItem="bxI-mu-qng" secondAttribute="top" constant="8" id="6Bh-e4-HVr"/>
<constraint firstAttribute="trailing" secondItem="DtR-jx-UKY" secondAttribute="trailing" id="K7y-Df-dgz"/>
<constraint firstItem="ZIv-LS-3Mg" firstAttribute="leading" secondItem="he8-pl-xE9" secondAttribute="leading" id="OYT-Q7-yDa"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="bxI-mu-qng" secondAttribute="leading" id="Q9n-7p-gHl"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" id="QSg-yz-aaB"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="Rg4-jV-Nht"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="20" id="Smz-GL-JdW"/>
<constraint firstItem="ZIv-LS-3Mg" firstAttribute="trailing" secondItem="he8-pl-xE9" secondAttribute="trailing" id="TZv-Rd-OBF"/>
<constraint firstItem="4aN-Cq-vqG" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="UBd-6J-ZuW"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="ZP8-mV-RBh"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" id="Zeg-U8-uis"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="leading" secondItem="he8-pl-xE9" secondAttribute="leading" id="bma-QK-Hut"/>
<constraint firstItem="MpQ-jR-g1m" firstAttribute="top" secondItem="ZIv-LS-3Mg" secondAttribute="bottom" constant="8" id="cbT-st-5j5"/>
<constraint firstItem="ZIv-LS-3Mg" firstAttribute="top" secondItem="4aN-Cq-vqG" secondAttribute="bottom" constant="8" id="fiq-v2-5EA"/>
<constraint firstItem="4aN-Cq-vqG" firstAttribute="top" secondItem="he8-pl-xE9" secondAttribute="bottom" constant="10" id="g4o-2j-hEe"/>
<constraint firstAttribute="width" priority="750" constant="375" id="glD-Sz-73O"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="hK0-aA-d9H"/>
<constraint firstItem="MpQ-jR-g1m" firstAttribute="leading" secondItem="he8-pl-xE9" secondAttribute="leading" id="hMj-hb-HYg"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="trailing" secondItem="he8-pl-xE9" secondAttribute="trailing" id="mYW-nM-bvw"/>
<constraint firstItem="MpQ-jR-g1m" firstAttribute="trailing" secondItem="he8-pl-xE9" secondAttribute="trailing" id="qbA-ez-dYG"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="35" id="s3k-Io-834"/>
<constraint firstAttribute="bottom" secondItem="DtR-jx-UKY" secondAttribute="bottom" constant="78" id="vi4-yP-gLN"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="top" secondItem="MpQ-jR-g1m" secondAttribute="bottom" constant="20" id="ymb-VV-LiR"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" constant="70" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="avatarImageView" destination="4aN-Cq-vqG" id="rDT-ne-J1g"/>
<outlet property="continueButton" destination="DOt-5E-FjF" id="EHR-Zq-tH5"/>
<outlet property="continueButtonBackgroundView" destination="DtR-jx-UKY" id="hc4-9Z-HeC"/>
<outlet property="description1Label" destination="6yX-xD-4X5" id="KV5-9h-BhU"/>
<outlet property="description2Label" destination="bxI-mu-qng" id="5bW-iT-67c"/>
<outlet property="deviceIdLabel" destination="MpQ-jR-g1m" id="KSN-yf-zFB"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="titleLabel" destination="he8-pl-xE9" id="4SL-xX-ETK"/>
<outlet property="userDisplaynameLabel" destination="ZIv-LS-3Mg" id="pHg-qH-yUb"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3199.1999999999998" y="-647.22638680659679"/>
</scene>
</scenes>
<resources>
<image name="favourite" width="32.5" height="28.5"/>
</resources>
</document>
@@ -0,0 +1,219 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class DeviceVerificationIncomingViewController: UIViewController {
// MARK: - Constants
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var avatarImageView: MXKImageView!
@IBOutlet weak var userDisplaynameLabel: UILabel!
@IBOutlet weak var deviceIdLabel: UILabel!
@IBOutlet weak var description1Label: UILabel!
@IBOutlet weak var description2Label: UILabel!
@IBOutlet weak var continueButtonBackgroundView: UIView!
@IBOutlet weak var continueButton: UIButton!
// MARK: Private
private var viewModel: DeviceVerificationIncomingViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: DeviceVerificationIncomingViewModelType) -> DeviceVerificationIncomingViewController {
let viewController = StoryboardScene.DeviceVerificationIncomingViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.deviceVerificationTitle
self.vc_removeBackTitle()
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.description1Label.textColor = theme.textPrimaryColor
self.description2Label.textColor = theme.textPrimaryColor
self.continueButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.continueButton)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.scrollView.keyboardDismissMode = .interactive
self.titleLabel.text = VectorL10n.deviceVerificationIncomingTitle
self.description1Label.text = VectorL10n.deviceVerificationIncomingDescription1
self.description2Label.text = VectorL10n.deviceVerificationIncomingDescription2
self.continueButton.setTitle(VectorL10n.continue, for: .normal)
if let avatarImageView = self.avatarImageView {
let defaultavatarImage = AvatarGenerator.generateAvatar(forMatrixItem: self.viewModel.userId, withDisplayName: self.viewModel.userDisplayName)
avatarImageView.enableInMemoryCache = true
avatarImageView.setImageURI(self.viewModel.avatarUrl, withType: nil, andImageOrientation: .up, previewImage: defaultavatarImage, mediaManager: self.viewModel.mediaManager)
avatarImageView.layer.cornerRadius = avatarImageView.frame.size.width / 2
avatarImageView.clipsToBounds = true
}
self.userDisplaynameLabel.text = self.viewModel.userDisplayName ?? self.viewModel.userId
self.deviceIdLabel.text = self.viewModel.deviceId
}
private func render(viewState: DeviceVerificationIncomingViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderAccepted()
case .cancelled(let reason):
self.renderCancelled(reason: reason)
case .cancelledByMe(let reason):
self.renderCancelledByMe(reason: reason)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderAccepted() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
private func renderCancelled(reason: MXTransactionCancelCode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
private func renderCancelledByMe(reason: MXTransactionCancelCode) {
if reason.value != MXTransactionCancelCode.user().value {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
// MARK: - Actions
@IBAction private func continueButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .accept)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - DeviceVerificationIncomingViewModelViewDelegate
extension DeviceVerificationIncomingViewController: DeviceVerificationIncomingViewModelViewDelegate {
func deviceVerificationIncomingViewModel(_ viewModel: DeviceVerificationIncomingViewModelType, didUpdateViewState viewSate: DeviceVerificationIncomingViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,116 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let transaction: MXIncomingSASTransaction
// MARK: Public
let userId: String
let userDisplayName: String?
let avatarUrl: String?
let deviceId: String
let mediaManager: MXMediaManager
weak var viewDelegate: DeviceVerificationIncomingViewModelViewDelegate?
weak var coordinatorDelegate: DeviceVerificationIncomingViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUser: MXUser, transaction: MXIncomingSASTransaction) {
self.session = session
self.transaction = transaction
self.userId = otherUser.userId
self.userDisplayName = otherUser.displayname
self.avatarUrl = otherUser.avatarUrl
self.deviceId = transaction.otherDeviceId
self.mediaManager = session.mediaManager
self.registerTransactionDidStateChangeNotification(transaction: transaction)
}
deinit {
}
// MARK: - Public
func process(viewAction: DeviceVerificationIncomingViewAction) {
switch viewAction {
case .accept:
self.acceptIncomingDeviceVerification()
case .cancel:
self.rejectIncomingDeviceVerification()
self.coordinatorDelegate?.deviceVerificationIncomingViewModelDidCancel(self)
}
}
// MARK: - Private
private func acceptIncomingDeviceVerification() {
self.update(viewState: .loading)
self.transaction.accept()
}
private func rejectIncomingDeviceVerification() {
self.transaction.cancel(with: MXTransactionCancelCode.user())
}
private func update(viewState: DeviceVerificationIncomingViewState) {
self.viewDelegate?.deviceVerificationIncomingViewModel(self, didUpdateViewState: viewState)
}
// MARK: - MXDeviceVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification(transaction: MXIncomingSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXIncomingSASTransaction else {
return
}
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.update(viewState: .loaded)
self.coordinatorDelegate?.deviceVerificationIncomingViewModel(self, didAcceptTransaction: self.transaction)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
}
@@ -0,0 +1,44 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationIncomingViewModelViewDelegate: class {
func deviceVerificationIncomingViewModel(_ viewModel: DeviceVerificationIncomingViewModelType, didUpdateViewState viewSate: DeviceVerificationIncomingViewState)
}
protocol DeviceVerificationIncomingViewModelCoordinatorDelegate: class {
func deviceVerificationIncomingViewModel(_ viewModel: DeviceVerificationIncomingViewModelType, didAcceptTransaction transaction: MXSASTransaction)
func deviceVerificationIncomingViewModelDidCancel(_ viewModel: DeviceVerificationIncomingViewModelType)
}
/// Protocol describing the view model used by `DeviceVerificationIncomingViewController`
protocol DeviceVerificationIncomingViewModelType {
var userId: String { get }
var userDisplayName: String? { get }
var avatarUrl: String? { get }
var deviceId: String { get }
var mediaManager: MXMediaManager { get }
var viewDelegate: DeviceVerificationIncomingViewModelViewDelegate? { get set }
var coordinatorDelegate: DeviceVerificationIncomingViewModelCoordinatorDelegate? { get set }
func process(viewAction: DeviceVerificationIncomingViewAction)
}
@@ -0,0 +1,28 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Incoming DeviceVerificationIncoming
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationIncomingViewController view state
enum DeviceVerificationIncomingViewState {
case loading
case loaded // accepted
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}
@@ -0,0 +1,70 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var deviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType
private let deviceVerificationDataLoadingViewController: DeviceVerificationDataLoadingViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: DeviceVerificationDataLoadingCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
self.session = session
let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
let deviceVerificationDataLoadingViewController = DeviceVerificationDataLoadingViewController.instantiate(with: deviceVerificationDataLoadingViewModel)
self.deviceVerificationDataLoadingViewModel = deviceVerificationDataLoadingViewModel
self.deviceVerificationDataLoadingViewController = deviceVerificationDataLoadingViewController
}
// MARK: - Public methods
func start() {
self.deviceVerificationDataLoadingViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.deviceVerificationDataLoadingViewController
}
}
// MARK: - DeviceVerificationDataLoadingViewModelCoordinatorDelegate
extension DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingViewModelCoordinatorDelegate {
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) {
self.delegate?.deviceVerificationDataLoadingCoordinator(self, didLoadUser: user, device: device)
}
func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType) {
self.delegate?.deviceVerificationDataLoadingCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationDataLoadingCoordinatorDelegate: class {
func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo)
func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType)
}
/// `DeviceVerificationDataLoadingCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol DeviceVerificationDataLoadingCoordinatorType: Coordinator, Presentable {
var delegate: DeviceVerificationDataLoadingCoordinatorDelegate? { get }
}
@@ -0,0 +1,24 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationDataLoadingViewController view actions exposed to view model
enum DeviceVerificationDataLoadingViewAction {
case cancel
}
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dBQ-CG-VDL">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Device Verification Data Loading View Controller-->
<scene sceneID="EyC-m5-6uM">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="dBQ-CG-VDL" customClass="DeviceVerificationDataLoadingViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ht4-fu-3rS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jOh-c7-uod">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="c4q-B8-hPy">
<rect key="frame" x="0.0" y="0.0" width="375" height="428"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fNE-v3-2lx">
<rect key="frame" x="0.0" y="0.0" width="375" height="428"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" priority="750" constant="500" id="9am-iX-rzi"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="fNE-v3-2lx" firstAttribute="top" secondItem="c4q-B8-hPy" secondAttribute="top" id="bHO-0I-Jjh"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="centerX" secondItem="c4q-B8-hPy" secondAttribute="centerX" id="fGs-s5-GHA"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="c4q-B8-hPy" secondAttribute="leading" id="jpJ-bp-Vmz"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fNE-v3-2lx" secondAttribute="trailing" id="juO-Zk-MPs"/>
<constraint firstAttribute="bottom" secondItem="fNE-v3-2lx" secondAttribute="bottom" id="sZa-ea-aZQ"/>
<constraint firstAttribute="height" constant="428" id="vx4-4u-WS1"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="c4q-B8-hPy" secondAttribute="bottom" id="KlD-dP-EYo"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="width" secondItem="jOh-c7-uod" secondAttribute="width" id="Tly-og-biF"/>
<constraint firstAttribute="trailing" secondItem="c4q-B8-hPy" secondAttribute="trailing" id="fNe-8B-X6c"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="h5p-NS-unN"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="top" secondItem="jOh-c7-uod" secondAttribute="top" id="zPm-BG-Pm8"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="jOh-c7-uod" secondAttribute="trailing" id="7K8-MG-xLT"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="bottom" secondItem="jOh-c7-uod" secondAttribute="bottom" id="DGP-MJ-g6l"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="leading" secondItem="Ht4-fu-3rS" secondAttribute="leading" id="TGc-b5-uMu"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="Z7r-yd-J4e"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="trailing" secondItem="6ex-OQ-2sZ" secondAttribute="trailing" id="jVN-Fr-MKN"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="top" secondItem="6ex-OQ-2sZ" secondAttribute="top" id="s7K-jf-P1z"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6ex-OQ-2sZ"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bLY-II-iJ3" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1703" y="255"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,143 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class DeviceVerificationDataLoadingViewController: UIViewController {
// MARK: - Properties
// MARK: Outlets
// MARK: Private
private var viewModel: DeviceVerificationDataLoadingViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: DeviceVerificationDataLoadingViewModelType) -> DeviceVerificationDataLoadingViewController {
let viewController = StoryboardScene.DeviceVerificationDataLoadingViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.deviceVerificationTitle
self.vc_removeBackTitle()
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
}
private func render(viewState: DeviceVerificationDataLoadingViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderLoaded()
case .error(let error):
self.render(error: error)
case .errorMessage(let message):
self.renderError(message: message)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
private func render(error: Error) {
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: {
self.viewModel.process(viewAction: .cancel)
})
}
private func renderError(message: String) {
self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: {
self.viewModel.process(viewAction: .cancel)
})
}
// MARK: - Actions
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - DeviceVerificationDataLoadingViewModelViewDelegate
extension DeviceVerificationDataLoadingViewController: DeviceVerificationDataLoadingViewModelViewDelegate {
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didUpdateViewState viewSate: DeviceVerificationDataLoadingViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,93 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let otherUserId: String
private let otherDeviceId: String
// MARK: Public
weak var viewDelegate: DeviceVerificationDataLoadingViewModelViewDelegate?
weak var coordinatorDelegate: DeviceVerificationDataLoadingViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
self.session = session
self.otherUserId = otherUserId
self.otherDeviceId = otherDeviceId
self.loadData()
}
deinit {
}
// MARK: - Public
func process(viewAction: DeviceVerificationDataLoadingViewAction) {
switch viewAction {
case .cancel:
self.coordinatorDelegate?.deviceVerificationDataLoadingViewModelDidCancel(self)
}
}
// MARK: - Private
private func loadData() {
self.update(viewState: .loading)
if let otherUser = self.session.user(withUserId: otherUserId) {
self.session.crypto?.downloadKeys([self.otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap) in
guard let sself = self else {
return
}
sself.update(viewState: .loaded)
if let otherDevice = usersDevicesMap?.object(forDevice: sself.otherDeviceId, forUser: sself.otherUserId) {
sself.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(sself, didLoadUser: otherUser, device: otherDevice)
} else {
sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
}
}, failure: { [weak self] (error) in
guard let sself = self, let error = error else {
return
}
sself.update(viewState: .error(error))
})
} else {
self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
}
}
private func update(viewState: DeviceVerificationDataLoadingViewState) {
self.viewDelegate?.deviceVerificationDataLoadingViewModel(self, didUpdateViewState: viewState)
}
}
@@ -0,0 +1,37 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationDataLoadingViewModelViewDelegate: class {
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didUpdateViewState viewSate: DeviceVerificationDataLoadingViewState)
}
protocol DeviceVerificationDataLoadingViewModelCoordinatorDelegate: class {
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo)
func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType)
}
/// Protocol describing the view model used by `DeviceVerificationDataLoadingViewController`
protocol DeviceVerificationDataLoadingViewModelType {
var viewDelegate: DeviceVerificationDataLoadingViewModelViewDelegate? { get set }
var coordinatorDelegate: DeviceVerificationDataLoadingViewModelCoordinatorDelegate? { get set }
func process(viewAction: DeviceVerificationDataLoadingViewAction)
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationDataLoadingViewController view state
enum DeviceVerificationDataLoadingViewState {
case loading
case loaded
case error(Error)
case errorMessage(String)
}
@@ -0,0 +1,78 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class DeviceVerificationStartCoordinator: DeviceVerificationStartCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var deviceVerificationStartViewModel: DeviceVerificationStartViewModelType
private let deviceVerificationStartViewController: DeviceVerificationStartViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: DeviceVerificationStartCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUser: MXUser, otherDevice: MXDeviceInfo) {
self.session = session
let deviceVerificationStartViewModel = DeviceVerificationStartViewModel(session: self.session, otherUser: otherUser, otherDevice: otherDevice)
let deviceVerificationStartViewController = DeviceVerificationStartViewController.instantiate(with: deviceVerificationStartViewModel)
self.deviceVerificationStartViewModel = deviceVerificationStartViewModel
self.deviceVerificationStartViewController = deviceVerificationStartViewController
}
// MARK: - Public methods
func start() {
self.deviceVerificationStartViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.deviceVerificationStartViewController
}
}
// MARK: - DeviceVerificationStartViewModelCoordinatorDelegate
extension DeviceVerificationStartCoordinator: DeviceVerificationStartViewModelCoordinatorDelegate {
func deviceVerificationStartViewModelDidUseLegacyVerification(_ viewModel: DeviceVerificationStartViewModelType) {
self.delegate?.deviceVerificationStartCoordinatorDidCancel(self)
}
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) {
self.delegate?.deviceVerificationStartCoordinator(self, didCompleteWithOutgoingTransaction: transaction)
}
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.deviceVerificationStartCoordinator(self, didTransactionCancelled: transaction)
}
func deviceVerificationStartViewModelDidCancel(_ viewModel: DeviceVerificationStartViewModelType) {
self.delegate?.deviceVerificationStartCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,31 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationStartCoordinatorDelegate: class {
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction)
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction)
func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType)
}
/// `DeviceVerificationStartCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol DeviceVerificationStartCoordinatorType: Coordinator, Presentable {
var delegate: DeviceVerificationStartCoordinatorDelegate? { get }
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationStartViewController view actions exposed to view model
enum DeviceVerificationStartViewAction {
case beginVerifying
case verifyUsingLegacy
case verifiedUsingLegacy
case cancel
}
@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Device Verification Start View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="DeviceVerificationStartViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="375" height="494"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="375" height="494"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verify by comparing a short text string" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="he8-pl-xE9">
<rect key="frame" x="20" y="35" width="335" height="41"/>
<constraints>
<constraint firstAttribute="height" constant="41" id="Nam-ca-50k"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="For maximum security, we recommend you do this in person or use another trusted means of communication." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="20" y="86" width="335" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="Y2u-fJ-idS"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Nothing appearing? Not all clients supports interactive verification yet. Use legacy verification." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xlN-AB-1V0">
<rect key="frame" x="20" y="316" width="335" height="100"/>
<constraints>
<constraint firstAttribute="height" constant="100" id="Pes-rZ-lpS"/>
<constraint firstAttribute="width" constant="335" id="caQ-aT-Sw4"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DtR-jx-UKY">
<rect key="frame" x="0.0" y="366" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="20" y="10" width="335" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Begin Verifying">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="verifyButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerY" secondItem="DtR-jx-UKY" secondAttribute="centerY" id="5eX-a5-zpP"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerX" secondItem="DtR-jx-UKY" secondAttribute="centerX" id="6v9-MN-mk2"/>
<constraint firstAttribute="trailing" secondItem="DOt-5E-FjF" secondAttribute="trailing" constant="20" id="7Cr-6b-kGn"/>
<constraint firstAttribute="height" constant="50" id="QNq-au-ZdL"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="leading" secondItem="DtR-jx-UKY" secondAttribute="leading" constant="20" id="YrM-nf-AuH"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EBO-vX-zj4">
<rect key="frame" x="20" y="424" width="335" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="oyt-F3-W0L"/>
</constraints>
<state key="normal" title="Use Legacy Verification">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="useLegacyVerificationButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="7j6-Xn-y8s"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Waiting for partner to accept..." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6yX-xD-4X5">
<rect key="frame" x="20" y="213" width="335" height="54"/>
<constraints>
<constraint firstAttribute="width" constant="335" id="9C0-ev-AVw"/>
<constraint firstAttribute="height" constant="54" id="pta-eP-0yH"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="EBO-vX-zj4" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" id="4d3-TM-5jh"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="47" id="6Bh-e4-HVr"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="bottom" secondItem="xlN-AB-1V0" secondAttribute="bottom" id="Chw-nj-X21"/>
<constraint firstAttribute="trailing" secondItem="EBO-vX-zj4" secondAttribute="trailing" constant="20" id="HGR-53-jN9"/>
<constraint firstItem="xlN-AB-1V0" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="K7S-YK-nPU"/>
<constraint firstAttribute="trailing" secondItem="DtR-jx-UKY" secondAttribute="trailing" id="K7y-Df-dgz"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="bxI-mu-qng" secondAttribute="leading" id="Q9n-7p-gHl"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" id="QSg-yz-aaB"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="Rg4-jV-Nht"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="200" id="Smz-GL-JdW"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="ZP8-mV-RBh"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" id="Zeg-U8-uis"/>
<constraint firstAttribute="width" priority="750" constant="375" id="glD-Sz-73O"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="hK0-aA-d9H"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="he8-pl-xE9" secondAttribute="top" constant="51" id="oX9-J1-Y9r"/>
<constraint firstAttribute="bottom" secondItem="EBO-vX-zj4" secondAttribute="bottom" constant="20" id="rbj-JL-Oyi"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="35" id="s3k-Io-834"/>
<constraint firstItem="EBO-vX-zj4" firstAttribute="top" secondItem="xlN-AB-1V0" secondAttribute="bottom" constant="8" id="wRB-mm-fD5"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" constant="70" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="informationLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="titleLabel" destination="he8-pl-xE9" id="btA-kv-E2B"/>
<outlet property="useLegacyVerificationButton" destination="EBO-vX-zj4" id="V9h-0k-ExA"/>
<outlet property="useLegacyVerificationLabel" destination="xlN-AB-1V0" id="k33-JB-Jnc"/>
<outlet property="verifyButton" destination="DOt-5E-FjF" id="ktw-U4-efQ"/>
<outlet property="verifyButtonBackgroundView" destination="DtR-jx-UKY" id="9yG-wP-u8A"/>
<outlet property="waitingPartnerLabel" destination="6yX-xD-4X5" id="fre-bc-Kma"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3199.1999999999998" y="-647.22638680659679"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,250 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class DeviceVerificationStartViewController: UIViewController {
// MARK: - Constants
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var waitingPartnerLabel: UILabel!
@IBOutlet private weak var useLegacyVerificationLabel: UILabel!
@IBOutlet private weak var verifyButtonBackgroundView: UIView!
@IBOutlet private weak var verifyButton: UIButton!
@IBOutlet private weak var useLegacyVerificationButton: UIButton!
// MARK: Private
private var viewModel: DeviceVerificationStartViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: DeviceVerificationStartViewModelType) -> DeviceVerificationStartViewController {
let viewController = StoryboardScene.DeviceVerificationStartViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.deviceVerificationTitle
self.vc_removeBackTitle()
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
self.waitingPartnerLabel.textColor = theme.textPrimaryColor
self.useLegacyVerificationLabel.textColor = theme.textPrimaryColor
self.verifyButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.verifyButton)
theme.applyStyle(onButton: self.useLegacyVerificationButton)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.scrollView.keyboardDismissMode = .interactive
self.titleLabel.text = VectorL10n.deviceVerificationStartTitle
self.informationLabel.text = VectorL10n.deviceVerificationSecurityAdvice
self.waitingPartnerLabel.text = VectorL10n.deviceVerificationStartWaitPartner
self.useLegacyVerificationLabel.text = VectorL10n.deviceVerificationStartUseLegacy
self.waitingPartnerLabel.isHidden = true
self.useLegacyVerificationLabel.isHidden = true
self.verifyButton.setTitle(VectorL10n.deviceVerificationStartVerifyButton, for: .normal)
self.useLegacyVerificationButton.setTitle(VectorL10n.deviceVerificationStartUseLegacyAction, for: .normal)
}
private func render(viewState: DeviceVerificationStartViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderStarted()
case .verifyUsingLegacy(let session, let deviceInfo):
self.renderVerifyUsingLegacy(session: session, deviceInfo: deviceInfo)
case .cancelled(let reason):
self.renderCancelled(reason: reason)
case .cancelledByMe(let reason):
self.renderCancelledByMe(reason: reason)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderStarted() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.verifyButtonBackgroundView.isHidden = true
self.waitingPartnerLabel.isHidden = false
self.useLegacyVerificationLabel.isHidden = false
}
private func renderVerifyUsingLegacy(session: MXSession, deviceInfo: MXDeviceInfo) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
guard let encryptionInfoView = EncryptionInfoView(deviceInfo: deviceInfo, andMatrixSession: session) else {
return
}
encryptionInfoView.delegate = self
// Skip the intro page
encryptionInfoView.displayLegacyVerificationScreen()
// Display the legacy verification view in full screen
// TODO: Do not reuse the legacy EncryptionInfoView and create a screen from scratch
self.view.vc_addSubViewMatchingParent(encryptionInfoView)
self.navigationController?.isNavigationBarHidden = true
}
private func renderCancelled(reason: MXTransactionCancelCode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
private func renderCancelledByMe(reason: MXTransactionCancelCode) {
if reason.value != MXTransactionCancelCode.user().value {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
// MARK: - Actions
@IBAction private func verifyButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .beginVerifying)
}
@IBAction private func useLegacyVerificationButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .verifyUsingLegacy)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - DeviceVerificationStartViewModelViewDelegate
extension DeviceVerificationStartViewController: DeviceVerificationStartViewModelViewDelegate {
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didUpdateViewState viewSate: DeviceVerificationStartViewState) {
self.render(viewState: viewSate)
}
}
// MARK: - DeviceVerificationStartViewModelViewDelegate
extension DeviceVerificationStartViewController: MXKEncryptionInfoViewDelegate {
func encryptionInfoView(_ encryptionInfoView: MXKEncryptionInfoView!, didDeviceInfoVerifiedChange deviceInfo: MXDeviceInfo!) {
self.viewModel.process(viewAction: .verifiedUsingLegacy)
}
func encryptionInfoViewDidClose(_ encryptionInfoView: MXKEncryptionInfoView!) {
self.viewModel.process(viewAction: .cancel)
}
}
@@ -0,0 +1,132 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let verificationManager: MXDeviceVerificationManager
private let otherUser: MXUser
private let otherDevice: MXDeviceInfo
private var transaction: MXSASTransaction!
// MARK: Public
weak var viewDelegate: DeviceVerificationStartViewModelViewDelegate?
weak var coordinatorDelegate: DeviceVerificationStartViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, otherUser: MXUser, otherDevice: MXDeviceInfo) {
self.session = session
self.verificationManager = session.crypto.deviceVerificationManager
self.otherUser = otherUser
self.otherDevice = otherDevice
}
deinit {
}
// MARK: - Public
func process(viewAction: DeviceVerificationStartViewAction) {
switch viewAction {
case .beginVerifying:
self.beginVerifying()
case .verifyUsingLegacy:
self.cancelTransaction()
self.update(viewState: .verifyUsingLegacy(self.session, self.otherDevice))
case .verifiedUsingLegacy:
self.coordinatorDelegate?.deviceVerificationStartViewModelDidUseLegacyVerification(self)
case .cancel:
self.cancelTransaction()
self.coordinatorDelegate?.deviceVerificationStartViewModelDidCancel(self)
}
}
// MARK: - Private
private func beginVerifying() {
self.update(viewState: .loading)
self.verificationManager.beginKeyVerification(withUserId: self.otherUser.userId, andDeviceId: self.otherDevice.deviceId, method: MXKeyVerificationMethodSAS, success: { [weak self] (transaction) in
guard let sself = self else {
return
}
guard let sasTransaction: MXOutgoingSASTransaction = transaction as? MXOutgoingSASTransaction else {
return
}
sself.transaction = sasTransaction
sself.registerTransactionDidStateChangeNotification(transaction: sasTransaction)
sself.update(viewState: .loaded)
}, failure: {[weak self] error in
self?.update(viewState: .error(error))
})
}
private func cancelTransaction() {
guard let transaction = self.transaction else {
return
}
transaction.cancel(with: MXTransactionCancelCode.user())
}
private func update(viewState: DeviceVerificationStartViewState) {
self.viewDelegate?.deviceVerificationStartViewModel(self, didUpdateViewState: viewState)
}
// MARK: - MXDeviceVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification(transaction: MXOutgoingSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXOutgoingSASTransaction else {
return
}
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.coordinatorDelegate?.deviceVerificationStartViewModel(self, didCompleteWithOutgoingTransaction: transaction)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
}
@@ -0,0 +1,40 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationStartViewModelViewDelegate: class {
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didUpdateViewState viewSate: DeviceVerificationStartViewState)
}
protocol DeviceVerificationStartViewModelCoordinatorDelegate: class {
func deviceVerificationStartViewModelDidUseLegacyVerification(_ viewModel: DeviceVerificationStartViewModelType)
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction)
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction)
func deviceVerificationStartViewModelDidCancel(_ viewModel: DeviceVerificationStartViewModelType)
}
/// Protocol describing the view model used by `DeviceVerificationStartViewController`
protocol DeviceVerificationStartViewModelType {
var viewDelegate: DeviceVerificationStartViewModelViewDelegate? { get set }
var coordinatorDelegate: DeviceVerificationStartViewModelCoordinatorDelegate? { get set }
func process(viewAction: DeviceVerificationStartViewAction)
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Start DeviceVerificationStart
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationStartViewController view state
enum DeviceVerificationStartViewState {
case loading
case loaded // started
case verifyUsingLegacy(MXSession, MXDeviceInfo)
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dBQ-CG-VDL">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Device Verification Verified View Controller-->
<scene sceneID="EyC-m5-6uM">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="dBQ-CG-VDL" customClass="DeviceVerificationVerifiedViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ht4-fu-3rS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jOh-c7-uod">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="c4q-B8-hPy">
<rect key="frame" x="0.0" y="0.0" width="375" height="432.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fNE-v3-2lx">
<rect key="frame" x="0.0" y="0.0" width="375" height="432.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verified!" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1Nw-CZ-lKr">
<rect key="frame" x="20" y="60" width="335" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="30" translatesAutoresizingMaskIntoConstraints="NO" id="12d-Dc-Rlv">
<rect key="frame" x="20" y="160.5" width="335" height="102"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You've successfully verified this device." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5dj-7z-eH5">
<rect key="frame" x="0.0" y="0.0" width="335" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Secure messages with this user are end-to-end encrypted and not able to be read by third parties." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="spd-ah-EYp">
<rect key="frame" x="0.0" y="48" width="335" height="54"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="4Ll-vk-JLe">
<rect key="frame" x="0.0" y="362.5" width="375" height="50"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Wbk-EX-kTs">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kgv-EZ-dF9">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="WcJ-IL-5KV"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
<state key="normal" title="Got it">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="validateButtonAction:" destination="dBQ-CG-VDL" eventType="touchUpInside" id="kpR-g5-ogv"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="kgv-EZ-dF9" firstAttribute="top" secondItem="Wbk-EX-kTs" secondAttribute="top" id="1mu-8N-etF"/>
<constraint firstAttribute="bottom" secondItem="kgv-EZ-dF9" secondAttribute="bottom" id="2ce-b1-aV5"/>
<constraint firstAttribute="trailing" secondItem="kgv-EZ-dF9" secondAttribute="trailing" id="OHz-zo-Uvl"/>
<constraint firstAttribute="width" priority="750" constant="500" id="eud-Ba-XSx"/>
<constraint firstItem="kgv-EZ-dF9" firstAttribute="leading" secondItem="Wbk-EX-kTs" secondAttribute="leading" id="gPb-HX-NWn"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="leading" secondItem="1Nw-CZ-lKr" secondAttribute="leading" id="5Kb-MI-4lP"/>
<constraint firstAttribute="width" priority="750" constant="500" id="9am-iX-rzi"/>
<constraint firstAttribute="trailing" secondItem="4Ll-vk-JLe" secondAttribute="trailing" id="CU4-Sr-hLT"/>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="trailing" secondItem="1Nw-CZ-lKr" secondAttribute="trailing" id="Cax-X9-xf8"/>
<constraint firstItem="4Ll-vk-JLe" firstAttribute="leading" secondItem="fNE-v3-2lx" secondAttribute="leading" id="FGu-8C-v1U"/>
<constraint firstItem="4Ll-vk-JLe" firstAttribute="top" secondItem="12d-Dc-Rlv" secondAttribute="bottom" constant="100" id="Hue-GK-ORf"/>
<constraint firstItem="1Nw-CZ-lKr" firstAttribute="leading" secondItem="fNE-v3-2lx" secondAttribute="leading" constant="20" id="LK5-9b-xDf"/>
<constraint firstItem="1Nw-CZ-lKr" firstAttribute="top" secondItem="fNE-v3-2lx" secondAttribute="top" constant="60" id="MUK-4D-vke"/>
<constraint firstAttribute="bottom" secondItem="4Ll-vk-JLe" secondAttribute="bottom" constant="20" id="Vn1-zQ-G8t"/>
<constraint firstAttribute="trailing" secondItem="1Nw-CZ-lKr" secondAttribute="trailing" constant="20" id="WKJ-Ta-cVg"/>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="centerX" secondItem="fNE-v3-2lx" secondAttribute="centerX" id="ksz-nC-DeX"/>
<constraint firstItem="12d-Dc-Rlv" firstAttribute="top" secondItem="1Nw-CZ-lKr" secondAttribute="bottom" constant="80" id="rhF-BF-2cR"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="fNE-v3-2lx" firstAttribute="top" secondItem="c4q-B8-hPy" secondAttribute="top" id="bHO-0I-Jjh"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="centerX" secondItem="c4q-B8-hPy" secondAttribute="centerX" id="fGs-s5-GHA"/>
<constraint firstItem="fNE-v3-2lx" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="c4q-B8-hPy" secondAttribute="leading" id="jpJ-bp-Vmz"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fNE-v3-2lx" secondAttribute="trailing" id="juO-Zk-MPs"/>
<constraint firstAttribute="bottom" secondItem="fNE-v3-2lx" secondAttribute="bottom" id="sZa-ea-aZQ"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="c4q-B8-hPy" secondAttribute="bottom" id="KlD-dP-EYo"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="width" secondItem="jOh-c7-uod" secondAttribute="width" id="Tly-og-biF"/>
<constraint firstAttribute="trailing" secondItem="c4q-B8-hPy" secondAttribute="trailing" id="fNe-8B-X6c"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="h5p-NS-unN"/>
<constraint firstItem="c4q-B8-hPy" firstAttribute="top" secondItem="jOh-c7-uod" secondAttribute="top" id="zPm-BG-Pm8"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="jOh-c7-uod" secondAttribute="trailing" id="7K8-MG-xLT"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="bottom" secondItem="jOh-c7-uod" secondAttribute="bottom" id="DGP-MJ-g6l"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="leading" secondItem="Ht4-fu-3rS" secondAttribute="leading" id="TGc-b5-uMu"/>
<constraint firstItem="6ex-OQ-2sZ" firstAttribute="leading" secondItem="jOh-c7-uod" secondAttribute="leading" id="Z7r-yd-J4e"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="trailing" secondItem="6ex-OQ-2sZ" secondAttribute="trailing" id="jVN-Fr-MKN"/>
<constraint firstItem="jOh-c7-uod" firstAttribute="top" secondItem="6ex-OQ-2sZ" secondAttribute="top" id="s7K-jf-P1z"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6ex-OQ-2sZ"/>
</view>
<connections>
<outlet property="description1Label" destination="5dj-7z-eH5" id="y4w-i9-5hB"/>
<outlet property="description2Label" destination="spd-ah-EYp" id="TXD-8P-gF0"/>
<outlet property="okButton" destination="kgv-EZ-dF9" id="do0-Ot-OKn"/>
<outlet property="okButtonBackgroundView" destination="Wbk-EX-kTs" id="2gv-gg-ROL"/>
<outlet property="titleLabel" destination="1Nw-CZ-lKr" id="zXP-Xt-Zl9"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bLY-II-iJ3" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1703" y="255"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,121 @@
// File created from simpleScreenTemplate
// $ createSimpleScreen.sh DeviceVerification/Verified DeviceVerificationVerified
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
protocol DeviceVerificationVerifiedViewControllerDelegate: class {
func deviceVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: DeviceVerificationVerifiedViewController)
func deviceVerificationVerifiedViewControllerDidCancel(_ viewController: DeviceVerificationVerifiedViewController)
}
final class DeviceVerificationVerifiedViewController: UIViewController {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var description1Label: UILabel!
@IBOutlet private weak var description2Label: UILabel!
@IBOutlet private weak var okButtonBackgroundView: UIView!
@IBOutlet private weak var okButton: UIButton!
// MARK: Private
private var theme: Theme!
// MARK: Public
weak var delegate: DeviceVerificationVerifiedViewControllerDelegate?
// MARK: - Setup
class func instantiate() -> DeviceVerificationVerifiedViewController {
let viewController = StoryboardScene.DeviceVerificationVerifiedViewController.initialScene.instantiate()
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.deviceVerificationTitle
self.vc_removeBackTitle()
self.setupViews()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide back button
self.navigationItem.setHidesBackButton(true, animated: animated)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func setupViews() {
self.titleLabel.text = VectorL10n.deviceVerificationVerifiedTitle
self.description1Label.text = VectorL10n.deviceVerificationVerifiedDescription1
self.description2Label.text = VectorL10n.deviceVerificationVerifiedDescription2
self.okButton.setTitle(VectorL10n.deviceVerificationVerifiedGotItButton, for: .normal)
}
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.description1Label.textColor = theme.textPrimaryColor
self.description2Label.textColor = theme.textPrimaryColor
self.okButtonBackgroundView.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.okButton)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
// MARK: - Actions
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
@IBAction private func validateButtonAction(_ sender: Any) {
self.delegate?.deviceVerificationVerifiedViewControllerDidTapSetupAction(self)
}
}
@@ -0,0 +1,71 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class DeviceVerificationVerifyCoordinator: DeviceVerificationVerifyCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var deviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModelType
private let deviceVerificationVerifyViewController: DeviceVerificationVerifyViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: DeviceVerificationVerifyCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, transaction: MXSASTransaction) {
self.session = session
let deviceVerificationVerifyViewModel = DeviceVerificationVerifyViewModel(session: self.session, transaction: transaction)
let deviceVerificationVerifyViewController = DeviceVerificationVerifyViewController.instantiate(with: deviceVerificationVerifyViewModel)
self.deviceVerificationVerifyViewModel = deviceVerificationVerifyViewModel
self.deviceVerificationVerifyViewController = deviceVerificationVerifyViewController
}
// MARK: - Public methods
func start() {
self.deviceVerificationVerifyViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.deviceVerificationVerifyViewController
}
}
// MARK: - DeviceVerificationVerifyViewModelCoordinatorDelegate
extension DeviceVerificationVerifyCoordinator: DeviceVerificationVerifyViewModelCoordinatorDelegate {
func deviceVerificationVerifyViewModelDidComplete(_ viewModel: DeviceVerificationVerifyViewModelType) {
self.delegate?.deviceVerificationVerifyCoordinatorDidComplete(self)
}
func deviceVerificationVerifyViewModelDidCancel(_ viewModel: DeviceVerificationVerifyViewModelType) {
self.delegate?.deviceVerificationVerifyCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationVerifyCoordinatorDelegate: class {
func deviceVerificationVerifyCoordinatorDidComplete(_ coordinator: DeviceVerificationVerifyCoordinatorType)
func deviceVerificationVerifyCoordinatorDidCancel(_ coordinator: DeviceVerificationVerifyCoordinatorType)
}
/// `DeviceVerificationVerifyCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol DeviceVerificationVerifyCoordinatorType: Coordinator, Presentable {
var delegate: DeviceVerificationVerifyCoordinatorDelegate? { get }
}
@@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationVerifyViewController view actions exposed to view model
enum DeviceVerificationVerifyViewAction {
case confirm
case complete
case cancel
}
@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Device Verification Verify View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="DeviceVerificationVerifyViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="375" height="485"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="375" height="485"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verify this device by confirming the following emoji appear on the screen of the partner" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="he8-pl-xE9">
<rect key="frame" x="20" y="35" width="335" height="61"/>
<constraints>
<constraint firstAttribute="height" constant="61" id="Nam-ca-50k"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="For maximum security, we recommend you do this in person or use another trusted means of communication." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="20" y="115" width="335" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="3ag-pn-F2b"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3152 3307 8179" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RD6-ue-X5c">
<rect key="frame" x="37.5" y="290.5" width="300" height="29"/>
<constraints>
<constraint firstAttribute="width" constant="300" id="6Q5-M2-ifj"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DtR-jx-UKY">
<rect key="frame" x="0.0" y="415" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="156.5" y="10" width="62" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Continue">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="continueButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerY" secondItem="DtR-jx-UKY" secondAttribute="centerY" id="5eX-a5-zpP"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="centerX" secondItem="DtR-jx-UKY" secondAttribute="centerX" id="6v9-MN-mk2"/>
<constraint firstAttribute="height" constant="50" id="QNq-au-ZdL"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Waiting for partner to confirm..." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6yX-xD-4X5">
<rect key="frame" x="20" y="242" width="335" height="54"/>
<constraints>
<constraint firstAttribute="width" constant="335" id="9C0-ev-AVw"/>
<constraint firstAttribute="height" constant="54" id="pta-eP-0yH"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="34i-3X-YvQ">
<rect key="frame" x="0.0" y="215" width="375" height="180"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="180" id="kpT-ty-CDI"/>
</constraints>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="0.0" id="mHm-Tg-xbO">
<size key="itemSize" width="80" height="80"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="VerifyEmojiCollectionViewCell" id="iG2-Pq-pYr" customClass="VerifyEmojiCollectionViewCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="🐶" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Cy-oI-To3">
<rect key="frame" x="0.0" y="6" width="80" height="43"/>
<fontDescription key="fontDescription" type="system" pointSize="36"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="dog" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="axP-53-KGQ">
<rect key="frame" x="0.0" y="58" width="80" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</view>
<constraints>
<constraint firstAttribute="trailing" secondItem="5Cy-oI-To3" secondAttribute="trailing" id="0vM-mM-9UI"/>
<constraint firstItem="axP-53-KGQ" firstAttribute="leading" secondItem="iG2-Pq-pYr" secondAttribute="leading" id="LeM-oa-STt"/>
<constraint firstItem="5Cy-oI-To3" firstAttribute="top" secondItem="iG2-Pq-pYr" secondAttribute="top" constant="6" id="ZBA-TS-ThT"/>
<constraint firstItem="5Cy-oI-To3" firstAttribute="leading" secondItem="iG2-Pq-pYr" secondAttribute="leading" id="hZz-hI-6k2"/>
<constraint firstAttribute="bottom" secondItem="axP-53-KGQ" secondAttribute="bottom" constant="6" id="ja0-Lm-Kej"/>
<constraint firstAttribute="trailing" secondItem="axP-53-KGQ" secondAttribute="trailing" id="yur-4a-SMc"/>
</constraints>
<connections>
<outlet property="emoji" destination="5Cy-oI-To3" id="pOP-P0-x8a"/>
<outlet property="name" destination="axP-53-KGQ" id="JYh-RY-edf"/>
</connections>
</collectionViewCell>
</cells>
<connections>
<outlet property="dataSource" destination="V8j-Lb-PgC" id="3m4-oH-KIj"/>
</connections>
</collectionView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="6yX-xD-4X5" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="47" id="6Bh-e4-HVr"/>
<constraint firstItem="34i-3X-YvQ" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="Bb1-EE-rq0"/>
<constraint firstAttribute="bottom" secondItem="DtR-jx-UKY" secondAttribute="bottom" constant="20" id="Cnb-UW-hYt"/>
<constraint firstAttribute="trailing" secondItem="DtR-jx-UKY" secondAttribute="trailing" id="K7y-Df-dgz"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="bxI-mu-qng" secondAttribute="leading" id="Q9n-7p-gHl"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" id="QSg-yz-aaB"/>
<constraint firstItem="6yX-xD-4X5" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="Rg4-jV-Nht"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="he8-pl-xE9" secondAttribute="top" constant="80" id="UL4-x0-oFT"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="centerX" secondItem="voD-3Q-ryt" secondAttribute="centerX" id="ZP8-mV-RBh"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" id="Zeg-U8-uis"/>
<constraint firstAttribute="width" priority="750" constant="375" id="glD-Sz-73O"/>
<constraint firstItem="RD6-ue-X5c" firstAttribute="centerY" secondItem="34i-3X-YvQ" secondAttribute="centerY" id="h8F-gc-do4"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="hK0-aA-d9H"/>
<constraint firstAttribute="trailing" secondItem="34i-3X-YvQ" secondAttribute="trailing" id="hg6-kq-M6U"/>
<constraint firstItem="RD6-ue-X5c" firstAttribute="centerX" secondItem="34i-3X-YvQ" secondAttribute="centerX" id="lVE-w6-pIa"/>
<constraint firstItem="DtR-jx-UKY" firstAttribute="top" secondItem="34i-3X-YvQ" secondAttribute="bottom" constant="20" id="liF-Qn-tiw"/>
<constraint firstItem="34i-3X-YvQ" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="20" id="r7A-9g-Mmb"/>
<constraint firstItem="he8-pl-xE9" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="35" id="s3k-Io-834"/>
</constraints>
<variation key="default">
<mask key="subviews">
<exclude reference="6yX-xD-4X5"/>
</mask>
</variation>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" constant="70" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="continueButton" destination="DOt-5E-FjF" id="ktw-U4-efQ"/>
<outlet property="continueButtonBackgroundView" destination="DtR-jx-UKY" id="9yG-wP-u8A"/>
<outlet property="decimalLabel" destination="RD6-ue-X5c" id="wlK-nl-CB6"/>
<outlet property="emojisCollectionView" destination="34i-3X-YvQ" id="wDE-oG-peo"/>
<outlet property="informationLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="titleLabel" destination="he8-pl-xE9" id="btA-kv-E2B"/>
<outlet property="waitingPartnerLabel" destination="6yX-xD-4X5" id="fre-bc-Kma"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3199.1999999999998" y="-647.22638680659679"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,251 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class DeviceVerificationVerifyViewController: UIViewController {
// MARK: - Constants
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var decimalLabel: UILabel!
@IBOutlet private weak var emojisCollectionView: UICollectionView!
@IBOutlet private weak var waitingPartnerLabel: UILabel!
@IBOutlet private weak var continueButtonBackgroundView: UIView!
@IBOutlet private weak var continueButton: UIButton!
// MARK: Private
private var viewModel: DeviceVerificationVerifyViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: DeviceVerificationVerifyViewModelType) -> DeviceVerificationVerifyViewController {
let viewController = StoryboardScene.DeviceVerificationVerifyViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.deviceVerificationTitle
self.vc_removeBackTitle()
self.setupViews()
self.errorPresenter = MXKErrorAlertPresentation()
self.activityPresenter = ActivityIndicatorPresenter()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide back button
self.navigationItem.setHidesBackButton(true, animated: animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
self.decimalLabel.textColor = theme.textPrimaryColor
self.waitingPartnerLabel.textColor = theme.textPrimaryColor
self.continueButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.continueButton)
emojisCollectionView.reloadData()
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.scrollView.keyboardDismissMode = .interactive
if viewModel.emojis != nil {
self.decimalLabel.isHidden = true
self.titleLabel.text = VectorL10n.deviceVerificationVerifyTitleEmoji
} else {
self.emojisCollectionView.isHidden = true
self.titleLabel.text = VectorL10n.deviceVerificationVerifyTitleNumber
self.decimalLabel.text = self.viewModel.decimal
}
self.informationLabel.text = VectorL10n.deviceVerificationSecurityAdvice
self.waitingPartnerLabel.text = VectorL10n.deviceVerificationVerifyWaitPartner
self.waitingPartnerLabel.isHidden = true
self.continueButton.setTitle(VectorL10n.continue, for: .normal)
}
private func render(viewState: DeviceVerificationVerifyViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded:
self.renderVerified()
case .cancelled(let reason):
self.renderCancelled(reason: reason)
case .cancelledByMe(let reason):
self.renderCancelledByMe(reason: reason)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderVerified() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.continueButtonBackgroundView.isHidden = true
self.waitingPartnerLabel.isHidden = false
}
private func renderCancelled(reason: MXTransactionCancelCode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
private func renderCancelledByMe(reason: MXTransactionCancelCode) {
if reason.value != MXTransactionCancelCode.user().value {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
// MARK: - Actions
@IBAction private func continueButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .confirm)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - DeviceVerificationVerifyViewModelViewDelegate
extension DeviceVerificationVerifyViewController: DeviceVerificationVerifyViewModelViewDelegate {
func deviceVerificationVerifyViewModel(_ viewModel: DeviceVerificationVerifyViewModelType, didUpdateViewState viewSate: DeviceVerificationVerifyViewState) {
self.render(viewState: viewSate)
}
}
extension DeviceVerificationVerifyViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let emojis = self.viewModel.emojis else {
return 0
}
return emojis.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VerifyEmojiCollectionViewCell", for: indexPath) as? VerifyEmojiCollectionViewCell else {
return UICollectionViewCell()
}
guard let emoji = self.viewModel.emojis?[indexPath.row] else {
return UICollectionViewCell()
}
cell.emoji.text = emoji.emoji
cell.name.text = VectorL10n.tr("Vector", "device_verification_emoji_\(emoji.name)")
cell.update(theme: self.theme)
return cell
}
}
@@ -0,0 +1,120 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let transaction: MXSASTransaction
// MARK: Public
weak var viewDelegate: DeviceVerificationVerifyViewModelViewDelegate?
weak var coordinatorDelegate: DeviceVerificationVerifyViewModelCoordinatorDelegate?
var emojis: [MXEmojiRepresentation]?
var decimal: String?
// MARK: - Setup
init(session: MXSession, transaction: MXSASTransaction) {
self.session = session
self.transaction = transaction
self.emojis = self.transaction.sasEmoji
self.decimal = self.transaction.sasDecimal
self.registerTransactionDidStateChangeNotification(transaction: transaction)
}
deinit {
}
// MARK: - Public
func process(viewAction: DeviceVerificationVerifyViewAction) {
switch viewAction {
case .confirm:
self.confirmTransaction()
case .complete:
self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidComplete(self)
case .cancel:
self.cancelTransaction()
self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidCancel(self)
}
}
// MARK: - Private
private func confirmTransaction() {
self.update(viewState: .loading)
self.transaction.confirmSASMatch()
}
private func cancelTransaction() {
self.transaction.cancel(with: MXTransactionCancelCode.user())
}
private func update(viewState: DeviceVerificationVerifyViewState) {
self.viewDelegate?.deviceVerificationVerifyViewModel(self, didUpdateViewState: viewState)
}
// MARK: - MXDeviceVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXSASTransaction else {
return
}
switch transaction.state {
case MXSASTransactionStateVerified:
self.update(viewState: .loaded)
self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidComplete(self)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateError:
guard let error = transaction.error else {
return
}
self.update(viewState: .error(error))
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
}
@@ -0,0 +1,40 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol DeviceVerificationVerifyViewModelViewDelegate: class {
func deviceVerificationVerifyViewModel(_ viewModel: DeviceVerificationVerifyViewModelType, didUpdateViewState viewSate: DeviceVerificationVerifyViewState)
}
protocol DeviceVerificationVerifyViewModelCoordinatorDelegate: class {
func deviceVerificationVerifyViewModelDidComplete(_ viewModel: DeviceVerificationVerifyViewModelType)
func deviceVerificationVerifyViewModelDidCancel(_ viewModel: DeviceVerificationVerifyViewModelType)
}
/// Protocol describing the view model used by `DeviceVerificationVerifyViewController`
protocol DeviceVerificationVerifyViewModelType {
var viewDelegate: DeviceVerificationVerifyViewModelViewDelegate? { get set }
var coordinatorDelegate: DeviceVerificationVerifyViewModelCoordinatorDelegate? { get set }
func process(viewAction: DeviceVerificationVerifyViewAction)
var emojis: [MXEmojiRepresentation]? { get set }
var decimal: String? { get set }
}
@@ -0,0 +1,28 @@
// File created from ScreenTemplate
// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// DeviceVerificationVerifyViewController view state
enum DeviceVerificationVerifyViewState {
case loading
case loaded // verified
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}
@@ -0,0 +1,26 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
class VerifyEmojiCollectionViewCell: UICollectionViewCell, Themable {
@IBOutlet weak var emoji: UILabel!
@IBOutlet weak var name: UILabel!
func update(theme: Theme) {
name.textColor = theme.textPrimaryColor
}
}
@@ -16,7 +16,16 @@
#import <MatrixKit/MatrixKit.h>
/**
TODO: This view as it is implemented in this class must disappear.
It should be part of the device verification flow (`DeviceVerificationCoordinator`).
*/
@interface EncryptionInfoView : MXKEncryptionInfoView
/**
Open the legacy simple verification screen
*/
- (void)displayLegacyVerificationScreen;
@end
@@ -20,6 +20,15 @@
#import "ThemeService.h"
#import "Riot-Swift.h"
#import "AppDelegate.h"
@interface EncryptionInfoView() <DeviceVerificationCoordinatorBridgePresenterDelegate>
{
DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter;
}
@end
@implementation EncryptionInfoView
#pragma mark - Override MXKView
@@ -37,4 +46,38 @@
self.confirmVerifyButton.tintColor = ThemeService.shared.theme.tintColor;
}
- (void)displayLegacyVerificationScreen
{
[super onButtonPressed:self.verifyButton];
}
- (void)onButtonPressed:(id)sender
{
UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController;
if (sender == self.verifyButton && self.mxDeviceInfo.verified != MXDeviceVerified
&& self.mxDeviceInfo
&& rootViewController)
{
// Redirect to the interactive device verification flow
deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:self.mxSession];
deviceVerificationCoordinatorBridgePresenter.delegate = self;
// Show it on the root view controller
[deviceVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:self.mxDeviceInfo.userId otherDeviceId:self.mxDeviceInfo.deviceId animated:YES];
}
else
{
[super onButtonPressed:sender];
}
}
- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId {
[deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
deviceVerificationCoordinatorBridgePresenter = nil;
// Eject like MXKEncryptionInfoView does
[self removeFromSuperview];
}
@end
@@ -232,8 +232,8 @@
#pragma mark - Private methods
- (void)openRoomWithId:(NSString*)roomId inMatrixSession:(MXSession*)mxSession
{
[[AppDelegate theDelegate].masterTabBarController selectRoomWithId:roomId andEventId:nil inMatrixSession:mxSession];
{
[[AppDelegate theDelegate] showRoom:roomId andEventId:nil withMatrixSession:mxSession restoreInitialDisplay:NO];
}
- (void)refreshCurrentSelectedCell:(BOOL)forceVisible
@@ -135,14 +135,7 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
}
// Use bold font for the room title
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
{
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold];
}
else
{
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont boldSystemFontOfSize:13];
}
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold];
}
else if (roomCellData.roomSummary.room.summary.membership == MXMembershipInvite)
{
@@ -155,26 +148,13 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = self.missedNotifAndUnreadBadgeLabel.frame.size.width + 18;
// Use bold font for the room title
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
{
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold];
}
else
{
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont boldSystemFontOfSize:13];
}
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold];
}
else
{
// The room title is not bold anymore
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
{
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
}
else
{
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13];
}
// The room title is not bold anymore
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
}
self.directRoomBorderView.hidden = !roomCellData.roomSummary.room.isDirect;
@@ -0,0 +1,70 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import JitsiMeet
/// JitsiService enables to abstract and configure Jitsi Meet SDK
@objcMembers
final class JitsiService: NSObject {
static let shared = JitsiService()
// MARK: - Properties
var enableCallKit: Bool = true {
didSet {
JMCallKitProxy.enabled = enableCallKit
}
}
private let jitsiMeet = JitsiMeet.sharedInstance()
// MARK: - Setup
private override init() {
super.init()
}
// MARK: - Public
// MARK: Configuration
func configureDefaultConferenceOptions(with serverURL: URL) {
self.jitsiMeet.defaultConferenceOptions = JitsiMeetConferenceOptions.fromBuilder({ (builder) in
builder.serverURL = serverURL
})
}
func configureCallKitProvider(localizedName: String, ringtoneName: String?, iconTemplateImageData: Data?) {
JMCallKitProxy.configureProvider(localizedName: localizedName, ringtoneSound: ringtoneName, iconTemplateImageData: iconTemplateImageData)
}
// MARK: AppDelegate methods
@discardableResult
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
return self.jitsiMeet.application(application, didFinishLaunchingWithOptions: launchOptions ?? [:])
}
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return self.jitsiMeet.application(application, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return self.jitsiMeet.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
@@ -16,8 +16,6 @@
#import <MatrixKit/MatrixKit.h>
#import <JitsiMeet/JitsiMeet.h>
#import "WidgetManager.h"
@protocol JitsiViewControllerDelegate;
@@ -28,7 +26,7 @@
https://github.com/jitsi/jitsi-meet/tree/master/ios
*/
@interface JitsiViewController : MXKViewController <JitsiMeetViewDelegate>
@interface JitsiViewController : MXKViewController
/**
Returns the `UINib` object initialized for a `JitsiViewController`.
@@ -75,12 +73,6 @@
*/
@property (nonatomic) id<JitsiViewControllerDelegate> delegate;
#pragma mark - Xib attributes
// The jitsi-meet SDK view
@property (weak, nonatomic) IBOutlet JitsiMeetView *jitsiMeetView;
@property (weak, nonatomic) IBOutlet UIButton *backToAppButton;
@end
@@ -15,15 +15,17 @@
*/
#import "JitsiViewController.h"
@import JitsiMeet;
static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
static const NSString *kJitsiDataErrorKey = @"error";
@interface JitsiViewController ()
{
NSString *jitsiUrl;
@interface JitsiViewController () <JitsiMeetViewDelegate>
BOOL video;
}
// The jitsi-meet SDK view
@property (nonatomic, weak) IBOutlet JitsiMeetView *jitsiMeetView;
@property (nonatomic, strong) NSString *conferenceId;
@property (nonatomic) BOOL startWithVideo;
@end
@@ -44,14 +46,41 @@ static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
return jitsiViewController;
}
#pragma mark - Life cycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.jitsiMeetView.delegate = self;
[self joinConference];
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Public
- (void)openWidget:(Widget*)widget withVideo:(BOOL)aVideo
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
video = aVideo;
self.startWithVideo = aVideo;
_widget = widget;
MXWeakify(self);
[_widget widgetUrl:^(NSString * _Nonnull widgetUrl) {
MXStrongifyAndReturnIfNil(self);
// Extract the jitsi conference id from the widget url
NSString *confId;
@@ -69,15 +98,11 @@ static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
break;
}
}
// And build from it the url to use in jitsi-meet sdk
if (confId)
{
jitsiUrl = [NSString stringWithFormat:@"%@%@", kJitsiServerUrl, confId];
}
}
self.conferenceId = confId;
if (jitsiUrl)
if (confId)
{
if (success)
{
@@ -107,71 +132,72 @@ static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
- (void)hangup
{
jitsiUrl = nil;
[self.jitsiMeetView leave];
}
// It would have been nicer to ask JitsiMeetView but there is no api.
// Dismissing the view controller and releasing it does the job for the moment
if (_delegate)
#pragma mark - Private
- (void)joinConference
{
[self joinConferenceWithId:self.conferenceId];
}
- (void)joinConferenceWithId:(NSString*)conferenceId
{
if (conferenceId)
{
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.jitsiMeetView.delegate = self;
// Pass the URL to jitsi-meet sdk
[self.jitsiMeetView loadURLObject: @{
@"url": jitsiUrl,
@"configOverwrite": @{
@"startWithVideoMuted": @(!video)
}
}];
// TODO: Set up user info but it is not yet available in the jitsi-meet iOS SDK
// See https://github.com/jitsi/jitsi-meet/issues/1880
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Actions
- (IBAction)onBackToAppButtonPressed:(id)sender
{
if (_delegate)
{
[_delegate jitsiViewController:self goBackToApp:nil];
// TODO: Set up user info but it is not yet available in the jitsi-meet iOS SDK
// See https://github.com/jitsi/jitsi-meet/issues/1880
JitsiMeetConferenceOptions *jitsiMeetConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder * _Nonnull jitsiMeetConferenceOptionsBuilder) {
jitsiMeetConferenceOptionsBuilder.room = conferenceId;
jitsiMeetConferenceOptionsBuilder.videoMuted = !self.startWithVideo;
}];
[self.jitsiMeetView join:jitsiMeetConferenceOptions];
}
}
#pragma mark - JitsiMeetViewDelegate
- (void)conferenceFailed:(NSDictionary *)data
- (void)conferenceWillJoin:(NSDictionary *)data
{
NSLog(@"[JitsiViewController] conferenceFailed - data: %@", data);
}
- (void)conferenceLeft:(NSDictionary *)data
- (void)conferenceJoined:(NSDictionary *)data
{
dispatch_async(dispatch_get_main_queue(), ^{
// The conference is over. Let the delegate close this view controller.
if (_delegate)
{
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
}
else
{
// Do it ourself
[self dismissViewControllerAnimated:YES completion:nil];
}
});
}
- (void)conferenceTerminated:(NSDictionary *)data
{
if (data[kJitsiDataErrorKey] != nil)
{
NSLog(@"[JitsiViewController] conferenceTerminated - data: %@", data);
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
// The conference is over. Let the delegate close this view controller.
if (self.delegate)
{
[self.delegate jitsiViewController:self dismissViewJitsiController:nil];
}
else
{
// Do it ourself
[self dismissViewControllerAnimated:YES completion:nil];
}
});
}
}
- (void)enterPictureInPicture:(NSDictionary *)data
{
if (self.delegate)
{
[self.delegate jitsiViewController:self goBackToApp:nil];
}
}
@end
@@ -1,17 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JitsiViewController">
<connections>
<outlet property="backToAppButton" destination="8tr-Cb-ue8" id="aUj-co-7JA"/>
<outlet property="jitsiMeetView" destination="7hL-Cs-mak" id="7kR-Te-Klw"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
@@ -25,41 +24,14 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8tr-Cb-ue8">
<rect key="frame" x="10" y="5" width="44" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="CallVCBackToAppButton"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="3id-6Q-PUF"/>
<constraint firstAttribute="width" constant="44" id="JGj-Jz-SbU"/>
</constraints>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
<color key="tintColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<inset key="titleEdgeInsets" minX="-69" minY="61" maxX="0.0" maxY="0.0"/>
<state key="normal" image="back_icon">
<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>
<state key="highlighted">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onBackToAppButtonPressed:" destination="-1" eventType="touchUpInside" id="2wo-nB-Rwd"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="7hL-Cs-mak" secondAttribute="trailing" id="8eH-2r-pjD"/>
<constraint firstAttribute="bottom" secondItem="7hL-Cs-mak" secondAttribute="bottom" id="BAo-6X-ovC"/>
<constraint firstItem="8tr-Cb-ue8" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="5" id="FPS-wy-gK6"/>
<constraint firstItem="8tr-Cb-ue8" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="10" id="Xca-R4-1cu"/>
<constraint firstItem="7hL-Cs-mak" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="s46-Fx-tT8"/>
<constraint firstItem="7hL-Cs-mak" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="x3v-Xl-cKi"/>
</constraints>
</view>
</objects>
<resources>
<image name="back_icon" width="13" height="23"/>
</resources>
</document>
@@ -83,7 +83,7 @@ final class KeyBackupBannerCell: MXKTableViewCell {
subtitle = VectorL10n.keyBackupSetupBannerSubtitle
case .recover:
title = VectorL10n.keyBackupRecoverBannerTitle
subtitle = VectorL10n.keyBackupRecoverBannerSubtitle
subtitle = VectorL10n.keyBackupRecoverConnentBannerSubtitle
case .none:
title = nil
subtitle = nil
@@ -95,7 +95,7 @@ final class KeyBackupSetupIntroViewController: UIViewController {
self.titleLabel.text = VectorL10n.keyBackupSetupIntroTitle
self.informationLabel.text = VectorL10n.keyBackupSetupIntroInfo
let setupTitle = self.isABackupAlreadyExists ? VectorL10n.keyBackupSetupIntroSetupActionWithExistingBackup : VectorL10n.keyBackupSetupIntroSetupActionWithoutExistingBackup
let setupTitle = self.isABackupAlreadyExists ? VectorL10n.keyBackupSetupIntroSetupConnectActionWithExistingBackup : VectorL10n.keyBackupSetupIntroSetupActionWithoutExistingBackup
self.setUpButton.setTitle(setupTitle, for: .normal)
@@ -75,32 +75,10 @@
*/
+ (instancetype)mediaPickerViewController;
@property (weak, nonatomic) IBOutlet UIScrollView *mainScrollView;
@property (weak, nonatomic) IBOutlet UIView *captureViewContainer;
//@property (weak, nonatomic) IBOutlet NSLayoutConstraint *captureViewContainerHeightConstraint;
@property (weak, nonatomic) IBOutlet UIView *cameraPreviewContainerView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *cameraPreviewContainerAspectRatio;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *cameraActivityIndicator;
@property (weak, nonatomic) IBOutlet UIButton *closeButton;
@property (weak, nonatomic) IBOutlet UIButton *cameraSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *cameraCaptureButton;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *cameraCaptureButtonWidthConstraint;
@property (weak, nonatomic) IBOutlet MXKPieChartView *cameraVideoCaptureProgressView;
@property (weak, nonatomic) IBOutlet UIView *recentCapturesCollectionContainerView;
@property (weak, nonatomic) IBOutlet UICollectionView *recentCapturesCollectionView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *recentCapturesCollectionContainerViewHeightConstraint;
@property (weak, nonatomic) IBOutlet UIView *libraryViewContainer;
@property (weak, nonatomic) IBOutlet UITableView *userAlbumsTableView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *libraryViewContainerViewHeightConstraint;
/**
The delegate for the view controller.
*/
@property (nonatomic) id<MediaPickerViewControllerDelegate> delegate;
@property (nonatomic, weak) id<MediaPickerViewControllerDelegate> delegate;
/**
The array of the media types supported by the picker (default value is an array containing kUTTypeImage).
@@ -94,6 +94,27 @@ static void *RecordingContext = &RecordingContext;
BOOL isStatusBarHidden;
}
@property (weak, nonatomic) IBOutlet UIScrollView *mainScrollView;
@property (weak, nonatomic) IBOutlet UIView *captureViewContainer;
@property (weak, nonatomic) IBOutlet UIView *cameraPreviewContainerView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *cameraPreviewContainerAspectRatio;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *cameraActivityIndicator;
@property (weak, nonatomic) IBOutlet UIButton *closeButton;
@property (weak, nonatomic) IBOutlet UIButton *cameraSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *cameraCaptureButton;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *cameraCaptureButtonWidthConstraint;
@property (weak, nonatomic) IBOutlet MXKPieChartView *cameraVideoCaptureProgressView;
@property (weak, nonatomic) IBOutlet UIView *recentCapturesCollectionContainerView;
@property (weak, nonatomic) IBOutlet UICollectionView *recentCapturesCollectionView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *recentCapturesCollectionContainerViewHeightConstraint;
@property (weak, nonatomic) IBOutlet UIView *libraryViewContainer;
@property (weak, nonatomic) IBOutlet UITableView *userAlbumsTableView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *libraryViewContainerViewHeightConstraint;
@property (nonatomic) UIBackgroundTaskIdentifier backgroundRecordingID;
@end
@@ -163,19 +184,24 @@ static void *RecordingContext = &RecordingContext;
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
MXWeakify(self);
// Observe UIApplicationWillEnterForegroundNotification to refresh captures collection when app leaves the background state.
UIApplicationWillEnterForegroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXStrongifyAndReturnIfNil(self);
[self reloadRecentCapturesCollection];
[self reloadUserLibraryAlbums];
}];
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXStrongifyAndReturnIfNil(self);
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
}
@@ -292,7 +318,7 @@ static void *RecordingContext = &RecordingContext;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.cameraActivityIndicator stopAnimating];
cameraPreviewLayer.hidden = NO;
self->cameraPreviewLayer.hidden = NO;
});
});
@@ -318,7 +344,7 @@ static void *RecordingContext = &RecordingContext;
if (granted)
{
// Load recent captures if this is not already done
if (!recentCaptures.count)
if (!self->recentCaptures.count)
{
dispatch_async(dispatch_get_main_queue(), ^{
@@ -564,9 +590,13 @@ static void *RecordingContext = &RecordingContext;
{
return;
}
MXWeakify(self);
dispatch_async(userAlbumsQueue, ^{
MXStrongifyAndReturnIfNil(self);
// List user albums which are not empty
PHFetchResult *albums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
@@ -575,9 +605,9 @@ static void *RecordingContext = &RecordingContext;
// Set up fetch options.
PHFetchOptions *options = [[PHFetchOptions alloc] init];
if ([_mediaTypes indexOfObject:(NSString *)kUTTypeImage] != NSNotFound)
if ([self->_mediaTypes indexOfObject:(NSString *)kUTTypeImage] != NSNotFound)
{
if ([_mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound)
if ([self->_mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound)
{
options.predicate = [NSPredicate predicateWithFormat:@"(mediaType == %d) || (mediaType == %d)", PHAssetMediaTypeImage, PHAssetMediaTypeVideo];
}
@@ -586,7 +616,7 @@ static void *RecordingContext = &RecordingContext;
options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %d",PHAssetMediaTypeImage];
}
}
else if ([_mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound)
else if ([self->_mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound)
{
options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %d",PHAssetMediaTypeVideo];
}
@@ -626,11 +656,11 @@ static void *RecordingContext = &RecordingContext;
dispatch_async(dispatch_get_main_queue(), ^{
userAlbums = updatedUserAlbums;
if (userAlbums.count)
self->userAlbums = updatedUserAlbums;
if (self->userAlbums.count)
{
self.userAlbumsTableView.hidden = NO;
self.libraryViewContainerViewHeightConstraint.constant = (userAlbums.count * 74);
self.libraryViewContainerViewHeightConstraint.constant = (self->userAlbums.count * 74);
[self.libraryViewContainer needsUpdateConstraints];
[self.userAlbumsTableView reloadData];
@@ -759,13 +789,13 @@ static void *RecordingContext = &RecordingContext;
}];
}
isValidationInProgress = NO;
self->isValidationInProgress = NO;
}];
}
else
{
NSLog(@"[MediaPickerVC] didSelectAsset: Failed to get image for asset");
isValidationInProgress = NO;
self->isValidationInProgress = NO;
// Alert user
NSError *error = info[@"PHImageErrorKey"];
@@ -815,20 +845,20 @@ static void *RecordingContext = &RecordingContext;
[self.delegate mediaPickerController:self didSelectVideo:[avURLAsset URL]];
}
isValidationInProgress = NO;
self->isValidationInProgress = NO;
}];
}
else
{
NSLog(@"[MediaPickerVC] Selected video asset is not initialized from an URL!");
isValidationInProgress = NO;
self->isValidationInProgress = NO;
}
}
else
{
NSLog(@"[MediaPickerVC] didSelectAsset: Failed to get image for asset");
isValidationInProgress = NO;
self->isValidationInProgress = NO;
// Alert user
NSError *error = info[@"PHImageErrorKey"];
@@ -1103,8 +1133,12 @@ static void *RecordingContext = &RecordingContext;
[self.cameraActivityIndicator startAnimating];
MXWeakify(self);
dispatch_async(cameraQueue, ^{
MXStrongifyAndReturnIfNil(self);
// Get the Camera Device
AVCaptureDevice *frontCamera = nil;
AVCaptureDevice *backCamera = nil;
@@ -1151,42 +1185,42 @@ static void *RecordingContext = &RecordingContext;
}
}
currentCameraInput = nil;
self->currentCameraInput = nil;
NSError *error = nil;
if (frontCamera)
{
frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:frontCamera error:&error];
self->frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:frontCamera error:&error];
if (error)
{
NSLog(@"[MediaPickerVC] Error: %@", error);
}
if (frontCameraInput == nil)
if (self->frontCameraInput == nil)
{
NSLog(@"[MediaPickerVC] Error creating front camera capture input");
}
else
{
currentCameraInput = frontCameraInput;
self->currentCameraInput = self->frontCameraInput;
}
}
if (backCamera)
{
error = nil;
backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:backCamera error:&error];
self->backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:backCamera error:&error];
if (error)
{
NSLog(@"[MediaPickerVC] Error: %@", error);
}
if (backCameraInput == nil)
if (self->backCameraInput == nil)
{
NSLog(@"[MediaPickerVC] Error creating back camera capture input");
}
else
{
currentCameraInput = backCameraInput;
self->currentCameraInput = self->backCameraInput;
}
}
@@ -1194,38 +1228,38 @@ static void *RecordingContext = &RecordingContext;
self.cameraSwitchButton.hidden = (!frontCamera || !backCamera);
});
if (currentCameraInput)
if (self->currentCameraInput)
{
// Create the AVCapture Session
captureSession = [[AVCaptureSession alloc] init];
self->captureSession = [[AVCaptureSession alloc] init];
if (isPictureCaptureEnabled)
if (self->isPictureCaptureEnabled)
{
[captureSession setSessionPreset:AVCaptureSessionPresetPhoto];
[self->captureSession setSessionPreset:AVCaptureSessionPresetPhoto];
}
else if (isVideoCaptureEnabled)
else if (self->isVideoCaptureEnabled)
{
[captureSession setSessionPreset:AVCaptureSessionPresetHigh];
[self->captureSession setSessionPreset:AVCaptureSessionPresetHigh];
}
cameraPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
cameraPreviewLayer.masksToBounds = NO;
cameraPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;//AVLayerVideoGravityResizeAspect;
cameraPreviewLayer.backgroundColor = [[UIColor blackColor] CGColor];
self->cameraPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self->captureSession];
self->cameraPreviewLayer.masksToBounds = NO;
self->cameraPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;//AVLayerVideoGravityResizeAspect;
self->cameraPreviewLayer.backgroundColor = [[UIColor blackColor] CGColor];
// cameraPreviewLayer.borderWidth = 2;
dispatch_async(dispatch_get_main_queue(), ^{
[[cameraPreviewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation]];
[cameraPreviewLayer connection].videoScaleAndCropFactor = 1.0;
cameraPreviewLayer.frame = self.cameraPreviewContainerView.bounds;
cameraPreviewLayer.hidden = YES;
[[self->cameraPreviewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation]];
[self->cameraPreviewLayer connection].videoScaleAndCropFactor = 1.0;
self->cameraPreviewLayer.frame = self.cameraPreviewContainerView.bounds;
self->cameraPreviewLayer.hidden = YES;
[self.cameraPreviewContainerView.layer addSublayer:cameraPreviewLayer];
[self.cameraPreviewContainerView.layer addSublayer:self->cameraPreviewLayer];
});
[captureSession addInput:currentCameraInput];
[self->captureSession addInput:self->currentCameraInput];
AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
@@ -1235,36 +1269,36 @@ static void *RecordingContext = &RecordingContext;
NSLog(@"[MediaPickerVC] Error: %@", error);
}
if ([captureSession canAddInput:audioDeviceInput])
if ([self->captureSession canAddInput:audioDeviceInput])
{
[captureSession addInput:audioDeviceInput];
[self->captureSession addInput:audioDeviceInput];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(caughtAVRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVCaptureSessionDidStartRunning:) name:AVCaptureSessionDidStartRunningNotification object:nil];
[captureSession startRunning];
[self->captureSession startRunning];
movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([captureSession canAddOutput:movieFileOutput])
self->movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([self->captureSession canAddOutput:self->movieFileOutput])
{
[captureSession addOutput:movieFileOutput];
AVCaptureConnection *connection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
[self->captureSession addOutput:self->movieFileOutput];
AVCaptureConnection *connection = [self->movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([connection isVideoStabilizationSupported])
{
// Available on iOS 8 and later
[connection setPreferredVideoStabilizationMode:YES];
}
}
[movieFileOutput addObserver:self forKeyPath:@"recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
[self->movieFileOutput addObserver:self forKeyPath:@"recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
if ([captureSession canAddOutput:stillImageOutput])
self->stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
if ([self->captureSession canAddOutput:self->stillImageOutput])
{
[stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}];
[captureSession addOutput:stillImageOutput];
[self->stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}];
[self->captureSession addOutput:self->stillImageOutput];
}
[stillImageOutput addObserver:self forKeyPath:@"capturingStillImage" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:CapturingStillImageContext];
[self->stillImageOutput addObserver:self forKeyPath:@"capturingStillImage" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:CapturingStillImageContext];
}
else
{
@@ -1273,7 +1307,7 @@ static void *RecordingContext = &RecordingContext;
});
}
isCaptureSessionSetupInProgress = NO;
self->isCaptureSessionSetupInProgress = NO;
});
}
@@ -1300,23 +1334,24 @@ static void *RecordingContext = &RecordingContext;
}
dispatch_sync(cameraQueue, ^{
frontCameraInput = nil;
backCameraInput = nil;
captureSession = nil;
self->frontCameraInput = nil;
self->backCameraInput = nil;
self->captureSession = nil;
if (movieFileOutput)
if (self->movieFileOutput)
{
[movieFileOutput removeObserver:self forKeyPath:@"recording" context:RecordingContext];
movieFileOutput = nil;
[self->movieFileOutput removeObserver:self forKeyPath:@"recording" context:RecordingContext];
self->movieFileOutput = nil;
}
if (stillImageOutput)
if (self->stillImageOutput)
{
[stillImageOutput removeObserver:self forKeyPath:@"capturingStillImage" context:CapturingStillImageContext];
stillImageOutput = nil;
[self->stillImageOutput removeObserver:self forKeyPath:@"capturingStillImage" context:CapturingStillImageContext];
self->stillImageOutput = nil;
}
currentCameraInput = nil;
self->currentCameraInput = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionRuntimeErrorNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionDidStartRunningNotification object:nil];
@@ -1342,7 +1377,7 @@ static void *RecordingContext = &RecordingContext;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.cameraActivityIndicator stopAnimating];
cameraPreviewLayer.hidden = NO;
self->cameraPreviewLayer.hidden = NO;
});
}
@@ -1357,45 +1392,45 @@ static void *RecordingContext = &RecordingContext;
if (frontCameraInput && backCameraInput)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!canToggleCamera)
if (!self->canToggleCamera)
{
return;
}
canToggleCamera = NO;
self->canToggleCamera = NO;
AVCaptureDeviceInput *newInput = nil;
AVCaptureDeviceInput *oldInput = nil;
if (currentCameraInput == frontCameraInput)
if (self->currentCameraInput == self->frontCameraInput)
{
newInput = backCameraInput;
oldInput = frontCameraInput;
newInput = self->backCameraInput;
oldInput = self->frontCameraInput;
}
else
{
newInput = frontCameraInput;
oldInput = backCameraInput;
newInput = self->frontCameraInput;
oldInput = self->backCameraInput;
}
dispatch_async(cameraQueue, ^{
dispatch_async(self->cameraQueue, ^{
[captureSession beginConfiguration];
[captureSession removeInput:oldInput];
if ([captureSession canAddInput:newInput]) {
[captureSession addInput:newInput];
currentCameraInput = newInput;
[self->captureSession beginConfiguration];
[self->captureSession removeInput:oldInput];
if ([self->captureSession canAddInput:newInput]) {
[self->captureSession addInput:newInput];
self->currentCameraInput = newInput;
}
[captureSession commitConfiguration];
[self->captureSession commitConfiguration];
dispatch_async(dispatch_get_main_queue(), ^{
[self.cameraActivityIndicator stopAnimating];
cameraPreviewLayer.hidden = NO;
canToggleCamera = YES;
self->cameraPreviewLayer.hidden = NO;
self->canToggleCamera = YES;
});
});
[self.cameraActivityIndicator startAnimating];
cameraPreviewLayer.hidden = YES;
self->cameraPreviewLayer.hidden = YES;
});
}
}
@@ -1404,10 +1439,15 @@ static void *RecordingContext = &RecordingContext;
{
self.cameraCaptureButton.enabled = NO;
MXWeakify(self);
dispatch_async(cameraQueue, ^{
if (![movieFileOutput isRecording])
MXStrongifyAndReturnIfNil(self);
if (![self->movieFileOutput isRecording])
{
lockInterfaceRotation = YES;
self->lockInterfaceRotation = YES;
if ([[UIDevice currentDevice] isMultitaskingSupported])
{
@@ -1425,14 +1465,14 @@ static void *RecordingContext = &RecordingContext;
}
// Update the orientation on the movie file output video connection before starting recording.
[[movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[cameraPreviewLayer connection] videoOrientation]];
[[self->movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[self->cameraPreviewLayer connection] videoOrientation]];
// Turning OFF flash for video recording
[MediaPickerViewController setFlashMode:AVCaptureFlashModeOff forDevice:[currentCameraInput device]];
[MediaPickerViewController setFlashMode:AVCaptureFlashModeOff forDevice:[self->currentCameraInput device]];
// Start recording to a temporary file.
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[@"movie" stringByAppendingPathExtension:@"mov"]];
[movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
[self->movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
}
});
}
@@ -1440,9 +1480,10 @@ static void *RecordingContext = &RecordingContext;
- (void)stopMovieRecording
{
dispatch_async(cameraQueue, ^{
if ([movieFileOutput isRecording])
if ([self->movieFileOutput isRecording])
{
[movieFileOutput stopRecording];
[self->movieFileOutput stopRecording];
}
});
}
@@ -1451,15 +1492,20 @@ static void *RecordingContext = &RecordingContext;
{
self.cameraCaptureButton.enabled = NO;
MXWeakify(self);
dispatch_async(cameraQueue, ^{
MXStrongifyAndReturnIfNil(self);
// Update the orientation on the still image output video connection before capturing.
[[stillImageOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[cameraPreviewLayer connection] videoOrientation]];
[[self->stillImageOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[self->cameraPreviewLayer connection] videoOrientation]];
// Flash set to Auto for Still Capture
[MediaPickerViewController setFlashMode:AVCaptureFlashModeAuto forDevice:[currentCameraInput device]];
[MediaPickerViewController setFlashMode:AVCaptureFlashModeAuto forDevice:[self->currentCameraInput device]];
// Capture a still image.
[stillImageOutput captureStillImageAsynchronouslyFromConnection:[stillImageOutput connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
[self->stillImageOutput captureStillImageAsynchronouslyFromConnection:[self->stillImageOutput connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer)
{
@@ -1504,10 +1550,10 @@ static void *RecordingContext = &RecordingContext;
- (void)runStillImageCaptureAnimation
{
dispatch_async(dispatch_get_main_queue(), ^{
[cameraPreviewLayer setOpacity:0.0];
[self->cameraPreviewLayer setOpacity:0.0];
[UIView animateWithDuration:.25 animations:^{
[cameraPreviewLayer setOpacity:1.0];
[self->cameraPreviewLayer setOpacity:1.0];
}];
});
}
@@ -1535,18 +1581,18 @@ static void *RecordingContext = &RecordingContext;
{
self.cameraSwitchButton.enabled = NO;
videoRecordStartDate = [NSDate date];
self->videoRecordStartDate = [NSDate date];
self.cameraVideoCaptureProgressView.hidden = NO;
updateVideoRecordingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateVideoRecordingDuration) userInfo:nil repeats:YES];
self->updateVideoRecordingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateVideoRecordingDuration) userInfo:nil repeats:YES];
self.cameraCaptureButton.enabled = YES;
}
else
{
self.cameraVideoCaptureProgressView.hidden = YES;
[updateVideoRecordingTimer invalidate];
updateVideoRecordingTimer = nil;
[self->updateVideoRecordingTimer invalidate];
self->updateVideoRecordingTimer = nil;
self.cameraVideoCaptureProgressView.progress = 0;
// The preview will be restored during captureOutput:didFinishRecordingToOutputFileAtURL: callback.
@@ -314,6 +314,8 @@
// Set the container tag to be able to retrieve read receipts container from component index (see component selection in MXKRoomBubbleTableViewCell (Vector) category).
avatarsContainer.tag = index;
avatarsContainer.moreLabelTextColor = ThemeService.shared.theme.textPrimaryColor;
[avatarsContainer refreshReceiptSenders:roomMembers withPlaceHolders:placeholders andAlignment:ReadReceiptAlignmentRight];
avatarsContainer.readReceipts = receipts;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:cell action:@selector(onReceiptContainerTap:)];
@@ -29,13 +29,11 @@
#import "TableViewCellWithButton.h"
#import "RoomTableViewCell.h"
#import "EncryptionInfoView.h"
#define TABLEVIEW_ROW_CELL_HEIGHT 46
#define TABLEVIEW_SECTION_HEADER_HEIGHT 28
#define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f
@interface RoomMemberDetailsViewController () <RoomMemberTitleViewDelegate>
@interface RoomMemberDetailsViewController () <RoomMemberTitleViewDelegate, DeviceVerificationCoordinatorBridgePresenterDelegate>
{
RoomMemberTitleView* memberTitleView;
@@ -62,7 +60,8 @@
*/
NSArray<MXDeviceInfo *> *devicesArray;
NSInteger devicesIndex;
EncryptionInfoView *encryptionInfoView;
DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter;
/**
Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg.
@@ -319,19 +318,6 @@
{
[super viewDidLayoutSubviews];
// Check here whether a subview has been added or removed
if (encryptionInfoView)
{
if (!encryptionInfoView.superview)
{
// Reset
encryptionInfoView = nil;
// Reload the full table to take into account a potential change on a device status.
[self updateMemberInfo];
}
}
// Check whether the title view has been created and rendered.
if (memberTitleView && memberTitleView.superview)
{
@@ -1154,50 +1140,10 @@
{
if (verificationStatus == MXDeviceVerified)
{
// Prompt the user before marking as verified the device.
encryptionInfoView = [[EncryptionInfoView alloc] initWithDeviceInfo:deviceTableViewCell.deviceInfo andMatrixSession:self.mxRoom.mxSession];
[encryptionInfoView onButtonPressed:encryptionInfoView.verifyButton];
// Add shadow on added view
encryptionInfoView.layer.cornerRadius = 5;
encryptionInfoView.layer.shadowOffset = CGSizeMake(0, 1);
encryptionInfoView.layer.shadowOpacity = 0.5f;
// Add the view and define edge constraints
[self.view addSubview:encryptionInfoView];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:10.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:-10.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:-10.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:10.0f]];
[self.view setNeedsUpdateConstraints];
deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
deviceVerificationCoordinatorBridgePresenter.delegate = self;
[deviceVerificationCoordinatorBridgePresenter presentFrom:self otherUserId:deviceTableViewCell.deviceInfo.userId otherDeviceId:deviceTableViewCell.deviceInfo.deviceId animated:YES];
}
else
{
@@ -1217,4 +1163,12 @@
[self viewDidLayoutSubviews];
}
#pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate
- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId
{
[deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
deviceVerificationCoordinatorBridgePresenter = nil;
}
@end
@@ -3561,6 +3561,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
[removedAlias addObject:roomAlias];
}
[self.tableView beginUpdates];
NSMutableIndexSet *mutableIndexSet = [NSMutableIndexSet indexSet];
if (roomAddresses.count <= 1)
@@ -3572,6 +3574,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
[mutableIndexSet addIndex:ROOM_SETTINGS_ROOM_ADDRESSES_SECTION_INDEX];
[self.tableView reloadSections:mutableIndexSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0);
}
}
@@ -3601,9 +3605,13 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
[removedGroup addObject:groupId];
}
[self.tableView beginUpdates];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:ROOM_SETTINGS_RELATED_GROUPS_SECTION_INDEX];
[self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0);
}
@@ -3636,6 +3644,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
[addedAlias addObject:roomAlias];
}
[self.tableView beginUpdates];
NSMutableIndexSet *mutableIndexSet = [NSMutableIndexSet indexSet];
if (!roomAddresses.count)
@@ -3663,6 +3673,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
[mutableIndexSet addIndex:ROOM_SETTINGS_ROOM_ADDRESSES_SECTION_INDEX];
[self.tableView reloadSections:mutableIndexSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0);
return YES;
@@ -3726,9 +3738,13 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
[addedGroup addObject:groupId];
}
[self.tableView beginUpdates];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:ROOM_SETTINGS_RELATED_GROUPS_SECTION_INDEX];
[self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0);
return YES;
@@ -35,12 +35,12 @@
@interface RoomInputToolbarView()
{
MediaPickerViewController *mediaPicker;
// The intermediate action sheet
UIAlertController *actionSheet;
}
@property(nonatomic, weak) MediaPickerViewController *mediaPicker;
@end
@implementation RoomInputToolbarView
@@ -391,11 +391,13 @@
Class PHAsset_class = NSClassFromString(@"PHAsset");
if (PHAsset_class)
{
mediaPicker = [MediaPickerViewController mediaPickerViewController];
MediaPickerViewController * mediaPicker = [MediaPickerViewController mediaPickerViewController];
mediaPicker.mediaTypes = @[(NSString *)kUTTypeImage, (NSString *)kUTTypeMovie];
mediaPicker.delegate = self;
UINavigationController *navigationController = [UINavigationController new];
[navigationController pushViewController:mediaPicker animated:NO];
self.mediaPicker = mediaPicker;
[self.delegate roomInputToolbarView:self presentViewController:navigationController];
}
@@ -448,11 +450,9 @@
- (void)dismissMediaPicker
{
if (mediaPicker)
if (self.mediaPicker)
{
[mediaPicker withdrawViewControllerAnimated:YES completion:nil];
[mediaPicker destroy];
mediaPicker = nil;
[self.mediaPicker withdrawViewControllerAnimated:YES completion:nil];
}
}
@@ -24,7 +24,7 @@
application asking the user if he wants to share room keys with a user's device.
For the moment, the user is himself.
*/
@interface RoomKeyRequestViewController : NSObject <MXKEncryptionInfoViewDelegate>
@interface RoomKeyRequestViewController : NSObject
/**
The UIAlertController instance which handles the dialog.
@@ -17,13 +17,13 @@
#import "RoomKeyRequestViewController.h"
#import "AppDelegate.h"
#import "EncryptionInfoView.h"
#import "Riot-Swift.h"
@interface RoomKeyRequestViewController ()
@interface RoomKeyRequestViewController () <DeviceVerificationCoordinatorBridgePresenterDelegate>
{
void (^onComplete)(void);
EncryptionInfoView *encryptionInfoView;
DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter;
BOOL wasNewDevice;
}
@@ -127,12 +127,6 @@
[_alertController dismissViewControllerAnimated:YES completion:nil];
_alertController = nil;
}
if (encryptionInfoView)
{
[encryptionInfoView removeFromSuperview];
encryptionInfoView = nil;
}
}
@@ -142,77 +136,43 @@
UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController;
if (rootViewController)
{
encryptionInfoView = [[EncryptionInfoView alloc] initWithDeviceInfo:_device andMatrixSession:_mxSession];
[encryptionInfoView onButtonPressed:encryptionInfoView.verifyButton];
deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:_mxSession];
deviceVerificationCoordinatorBridgePresenter.delegate = self;
encryptionInfoView.delegate = self;
// Add shadow on added view
encryptionInfoView.layer.cornerRadius = 5;
encryptionInfoView.layer.shadowOffset = CGSizeMake(0, 1);
encryptionInfoView.layer.shadowOpacity = 0.5f;
// Add the view and define edge constraints
[rootViewController.view addSubview:encryptionInfoView];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:rootViewController.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:10.0f]];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:rootViewController.bottomLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:-10.0f]];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:rootViewController.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:-10.0f]];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:rootViewController.view
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:10.0f]];
[rootViewController.view setNeedsUpdateConstraints];
[deviceVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:_device.userId otherDeviceId:_device.deviceId animated:YES];
}
}
#pragma mark - MXKEncryptionInfoViewDelegate
#pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate
- (void)encryptionInfoView:(MXKEncryptionInfoView *)theEncryptionInfoView didDeviceInfoVerifiedChange:(MXDeviceInfo *)deviceInfo
- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId
{
encryptionInfoView = nil;
[deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
deviceVerificationCoordinatorBridgePresenter = nil;
if (deviceInfo.verified == MXDeviceVerified)
{
// Accept the received requests from this device
// As the device is now verified, all other key requests will be automatically accepted.
[self.mxSession.crypto acceptAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{
// Check device new status
[self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap) {
onComplete();
}];
}
}
MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:self.device.deviceId forUser:self.device.userId];
if (deviceInfo && deviceInfo.verified == MXDeviceVerified)
{
// Accept the received requests from this device
// As the device is now verified, all other key requests will be automatically accepted.
[self.mxSession.crypto acceptAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{
- (void)encryptionInfoViewDidClose:(MXKEncryptionInfoView *)theEncryptionInfoView
{
encryptionInfoView = nil;
onComplete();
}];
}
else
{
// Come back to self.alertController - ie, reopen it
[self show];
}
} failure:^(NSError *error) {
// Come back to self.alertController - ie, reopen it
[self show];
// Should not happen (the device is in the crypto db)
[self show];
}];
}
@end
+1 -1
View File
@@ -260,7 +260,7 @@
if ([recentsDataSource.publicRoomsDirectoryDataSource.mxSession roomWithRoomId:publicRoom.roomId])
{
// Open the public room
[[AppDelegate theDelegate].masterTabBarController selectRoomWithId:publicRoom.roomId andEventId:nil inMatrixSession:recentsDataSource.publicRoomsDirectoryDataSource.mxSession];
[[AppDelegate theDelegate] showRoom:publicRoom.roomId andEventId:nil withMatrixSession:recentsDataSource.publicRoomsDirectoryDataSource.mxSession restoreInitialDisplay:NO];
}
else
{
@@ -31,6 +31,13 @@ import UIKit
func settingsKeyBackup(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, showError error: Error)
}
private enum BackupRows {
case info(text: String)
case createAction
case restoreAction(keyBackupVersion: MXKeyBackupVersion, title: String)
case deleteAction(keyBackupVersion: MXKeyBackupVersion)
}
@objc final class SettingsKeyBackupTableViewSection: NSObject {
// MARK: - Properties
@@ -44,9 +51,15 @@ import UIKit
private var viewModel: SettingsKeyBackupViewModelType!
// Need to know the state to make `cellForRow` deliver cells accordingly
private var viewState: SettingsKeyBackupViewState = .checkingBackup
private var viewState: SettingsKeyBackupViewState = .checkingBackup {
didSet {
self.updateBackupRows()
}
}
private var userDevice: MXDeviceInfo
private var backupRows: [BackupRows] = []
// MARK: - Public
@@ -58,53 +71,32 @@ import UIKit
self.viewModel.process(viewAction: .load)
}
@objc func numberOfRows() -> Int {
var numberOfRows: Int
switch self.viewState {
case .checkingBackup:
numberOfRows = self.numberOfCheckingBackupRows()
case .noBackup:
numberOfRows = self.numberOfNoBackupRows()
case .backup:
numberOfRows = self.numberOfBackupRows()
case .backupAndRunning:
numberOfRows = self.numberOfBackupAndRunningRows()
case .backupNotTrusted:
numberOfRows = self.numberOfBackupNotTrustedRows()
}
return numberOfRows
return self.backupRows.count
}
@objc func cellForRow(atRow row: Int) -> UITableViewCell {
var cell: UITableViewCell
switch self.viewState {
case .checkingBackup:
cell = self.renderCheckingBackupCell(atRow: row)
case .noBackup:
cell = self.renderNoBackupCell(atRow: row)
case .backup(let keyBackupVersion, let keyBackupVersionTrust):
cell = self.renderBackupCell(atRow: row,
keyBackupVersion: keyBackupVersion,
keyBackupVersionTrust: keyBackupVersionTrust)
case .backupAndRunning(let keyBackupVersion, let keyBackupVersionTrust, let backupProgress):
cell = self.renderBackupAndRunningCell(atRow: row,
keyBackupVersion: keyBackupVersion,
keyBackupVersionTrust: keyBackupVersionTrust,
backupProgress: backupProgress)
case .backupNotTrusted(let keyBackupVersion, let keyBackupVersionTrust):
cell = self.renderBackupNotTrustedCell(atRow: row,
keyBackupVersion: keyBackupVersion,
keyBackupVersionTrust: keyBackupVersionTrust)
guard let delegate = self.delegate else {
return UITableViewCell()
}
let backupRow = self.backupRows[row]
var cell: UITableViewCell
switch backupRow {
case .info(let infoText):
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
infoCell.mxkTextView.text = infoText
cell = infoCell
case .createAction:
cell = self.buttonCellForCreate(atRow: row)
case .restoreAction(keyBackupVersion: let keyBackupVersion, let title):
cell = self.buttonCellForRestore(keyBackupVersion: keyBackupVersion, title: title, atRow: row)
case .deleteAction(keyBackupVersion: let keyBackupVersion):
cell = self.buttonCellForDelete(keyBackupVersion: keyBackupVersion, atRow: row)
}
return cell
}
@@ -116,236 +108,135 @@ import UIKit
self.viewModel.process(viewAction: .delete(keyBackupVersion))
}
// MARK: - Pseudo TableView datasource
private func numberOfCheckingBackupRows() -> Int {
return 1
}
private func renderCheckingBackupCell(atRow row: Int) -> UITableViewCell {
guard let delegate = self.delegate else {
return UITableViewCell()
}
let cell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let info = VectorL10n.settingsKeyBackupInfo
let checking = VectorL10n.settingsKeyBackupInfoChecking
let strings = [info, "", checking]
cell.mxkTextView.text = strings.joined(separator: "\n")
return cell
}
private func numberOfNoBackupRows() -> Int {
return 2
}
private func renderNoBackupCell(atRow row: Int) -> UITableViewCell {
guard let delegate = self.delegate else {
return UITableViewCell()
}
var cell: UITableViewCell
switch row {
case 0:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
// MARK: - Data Computing
private func updateBackupRows() {
let backupRows: [BackupRows]
switch self.viewState {
case .checkingBackup:
let info = VectorL10n.settingsKeyBackupInfo
let checking = VectorL10n.settingsKeyBackupInfoChecking
let strings = [info, "", checking]
let text = strings.joined(separator: "\n")
backupRows = [
.info(text: text)
]
case .noBackup:
let noBackup = VectorL10n.settingsKeyBackupInfoNone
let info = VectorL10n.settingsKeyBackupInfo
let signoutWarning = VectorL10n.settingsKeyBackupInfoSignoutWarning
let strings = [noBackup, "", info, "", signoutWarning]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 1:
cell = self.buttonCellForCreate(atRow: row)
default:
cell = UITableViewCell()
}
return cell
}
private func numberOfBackupRows() -> Int {
return 5
}
private func renderBackupCell(atRow row: Int, keyBackupVersion: MXKeyBackupVersion, keyBackupVersionTrust: MXKeyBackupVersionTrust) -> UITableViewCell {
guard let delegate = self.delegate else {
return UITableViewCell()
}
var cell: UITableViewCell
switch row {
case 0:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let backupInfoText = strings.joined(separator: "\n")
backupRows = [
.info(text: backupInfoText),
.createAction
]
case .backup(let keyBackupVersion, let keyBackupVersionTrust):
let info = VectorL10n.settingsKeyBackupInfo
let backupStatus = VectorL10n.settingsKeyBackupInfoValid
let strings = [info, "", backupStatus]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 1:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let backupStrings = [info, "", backupStatus]
let backupInfoText = backupStrings.joined(separator: "\n")
let version = VectorL10n.settingsKeyBackupInfoVersion(keyBackupVersion.version ?? "")
let algorithm = VectorL10n.settingsKeyBackupInfoAlgorithm(keyBackupVersion.algorithm)
let uploadStatus = VectorL10n.settingsKeyBackupInfoProgressDone
let strings = [version, algorithm, uploadStatus]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 2:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let additionalStrings = [version, algorithm, uploadStatus]
let additionalInfoText = additionalStrings.joined(separator: "\n")
let backupTrust = self.stringForKeyBackupTrust(keyBackupVersionTrust)
infoCell.mxkTextView.text = backupTrust.joined(separator: "\n")
cell = infoCell
case 3:
cell = self.buttonCellForRestore(keyBackupVersion: keyBackupVersion, atRow: row)
case 4:
cell = self.buttonCellForDelete(keyBackupVersion: keyBackupVersion, atRow: row)
default:
cell = UITableViewCell()
}
return cell
}
private func numberOfBackupAndRunningRows() -> Int {
return 5
}
private func renderBackupAndRunningCell(atRow row: Int, keyBackupVersion: MXKeyBackupVersion, keyBackupVersionTrust: MXKeyBackupVersionTrust, backupProgress: Progress) -> UITableViewCell {
guard let delegate = self.delegate else {
return UITableViewCell()
}
var cell: UITableViewCell
switch row {
case 0:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: 0)
let backupTrustInfoText = backupTrust.joined(separator: "\n")
var backupViewStateRows: [BackupRows] = [
.info(text: backupInfoText),
.info(text: additionalInfoText),
.info(text: backupTrustInfoText)
]
// TODO: Do not display restore button if all keys are stored on the device
if true {
backupViewStateRows.append(.restoreAction(keyBackupVersion: keyBackupVersion, title: VectorL10n.settingsKeyBackupButtonRestore))
}
backupViewStateRows.append(.deleteAction(keyBackupVersion: keyBackupVersion))
backupRows = backupViewStateRows
case .backupAndRunning(let keyBackupVersion, let keyBackupVersionTrust, let backupProgress):
let info = VectorL10n.settingsKeyBackupInfo
let backupStatus = VectorL10n.settingsKeyBackupInfoValid
let strings = [info, "", backupStatus]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 1:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let backupStrings = [info, "", backupStatus]
let backupInfoText = backupStrings.joined(separator: "\n")
let remaining = backupProgress.totalUnitCount - backupProgress.completedUnitCount
let version = VectorL10n.settingsKeyBackupInfoVersion(keyBackupVersion.version ?? "")
let algorithm = VectorL10n.settingsKeyBackupInfoAlgorithm(keyBackupVersion.algorithm)
let uploadStatus = VectorL10n.settingsKeyBackupInfoProgress(String(remaining))
let strings = [version, algorithm, uploadStatus]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 2:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let additionalStrings = [version, algorithm, uploadStatus]
let additionalInfoText = additionalStrings.joined(separator: "\n")
let backupTrust = self.stringForKeyBackupTrust(keyBackupVersionTrust)
infoCell.mxkTextView.text = backupTrust.joined(separator: "\n")
cell = infoCell
case 3:
cell = self.buttonCellForRestore(keyBackupVersion: keyBackupVersion, atRow: row)
case 4:
cell = self.buttonCellForDelete(keyBackupVersion: keyBackupVersion, atRow: row)
default:
cell = UITableViewCell()
}
return cell
}
private func numberOfBackupNotTrustedRows() -> Int {
return 5
}
private func renderBackupNotTrustedCell(atRow row: Int, keyBackupVersion: MXKeyBackupVersion, keyBackupVersionTrust: MXKeyBackupVersionTrust) -> UITableViewCell {
guard let delegate = self.delegate else {
return UITableViewCell()
}
var cell: UITableViewCell
switch row {
case 0:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let backupTrustInfoText = backupTrust.joined(separator: "\n")
var backupAndRunningViewStateRows: [BackupRows] = [
.info(text: backupInfoText),
.info(text: additionalInfoText),
.info(text: backupTrustInfoText)
]
// TODO: Do not display restore button if all keys are stored on the device
if true {
backupAndRunningViewStateRows.append(.restoreAction(keyBackupVersion: keyBackupVersion, title: VectorL10n.settingsKeyBackupButtonRestore))
}
backupAndRunningViewStateRows.append(.deleteAction(keyBackupVersion: keyBackupVersion))
backupRows = backupAndRunningViewStateRows
case .backupNotTrusted(let keyBackupVersion, let keyBackupVersionTrust):
let info = VectorL10n.settingsKeyBackupInfo
let backupStatus = VectorL10n.settingsKeyBackupInfoNotValid
let signoutWarning = VectorL10n.settingsKeyBackupInfoSignoutWarning
let strings = [info, "", backupStatus, "", signoutWarning]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 1:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let backupStrings = [info, "", backupStatus, "", signoutWarning]
let backupInfoText = backupStrings.joined(separator: "\n")
let version = VectorL10n.settingsKeyBackupInfoVersion(keyBackupVersion.version ?? "")
let algorithm = VectorL10n.settingsKeyBackupInfoAlgorithm(keyBackupVersion.algorithm)
let strings = [version, algorithm]
infoCell.mxkTextView.text = strings.joined(separator: "\n")
cell = infoCell
case 2:
let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
let additionalStrings = [version, algorithm]
let additionalInfoText = additionalStrings.joined(separator: "\n")
let backupTrust = self.stringForKeyBackupTrust(keyBackupVersionTrust)
infoCell.mxkTextView.text = backupTrust.joined(separator: "\n")
cell = infoCell
case 3:
cell = self.buttonCellForRestore(keyBackupVersion: keyBackupVersion, atRow: row, title: VectorL10n.settingsKeyBackupButtonUse)
case 4:
cell = self.buttonCellForDelete(keyBackupVersion: keyBackupVersion, atRow: row)
default:
cell = UITableViewCell()
let backupTrustInfoText = backupTrust.joined(separator: "\n")
var backupNotTrustedViewStateRows: [BackupRows] = [
.info(text: backupInfoText),
.info(text: additionalInfoText),
.info(text: backupTrustInfoText)
]
// TODO: Do not display restore button if all keys are stored on the device
if true {
backupNotTrustedViewStateRows.append(.restoreAction(keyBackupVersion: keyBackupVersion, title: VectorL10n.settingsKeyBackupButtonConnect))
}
backupNotTrustedViewStateRows.append(.deleteAction(keyBackupVersion: keyBackupVersion))
backupRows = backupNotTrustedViewStateRows
}
return cell
self.backupRows = backupRows
}
// MARK: - Data Computing
private func stringForKeyBackupTrust(_ keyBackupVersionTrust: MXKeyBackupVersionTrust) -> [String] {
return keyBackupVersionTrust.signatures.map { (signature) -> String in
@@ -392,7 +283,7 @@ import UIKit
return cell
}
private func buttonCellForRestore(keyBackupVersion: MXKeyBackupVersion, atRow row: Int, title: String = VectorL10n.settingsKeyBackupButtonRestore) -> UITableViewCell {
private func buttonCellForRestore(keyBackupVersion: MXKeyBackupVersion, title: String, atRow row: Int) -> UITableViewCell {
guard let delegate = self.delegate else {
return UITableViewCell()
}
+6 -64
View File
@@ -48,8 +48,6 @@
#import "Riot-Swift.h"
#import "EncryptionInfoView.h"
NSString* const kSettingsViewControllerPhoneBookCountryCellId = @"kSettingsViewControllerPhoneBookCountryCellId";
enum
@@ -622,12 +620,15 @@ SignOutAlertPresenterDelegate>
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView beginUpdates];
// Refresh the corresponding table view cell with animation
[self.tableView reloadRowsAtIndexPaths:@[
[NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]
]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
});
}
}
@@ -648,12 +649,15 @@ SignOutAlertPresenterDelegate>
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView beginUpdates];
// Refresh the corresponding table view cell with animation
[self.tableView reloadRowsAtIndexPaths:@[
[NSIndexPath indexPathForRow:userSettingsNewPhoneIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]
]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
});
}
}
@@ -4345,68 +4349,6 @@ SignOutAlertPresenterDelegate>
[[AppDelegate theDelegate] showErrorAsAlert:error];
}
#pragma mark - MXKEncryptionInfoView
- (void)showDeviceInfo:(MXDeviceInfo*)deviceInfo
{
// Show it modally on the root view controller
// TODO: Improve it
UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController;
if (rootViewController)
{
EncryptionInfoView *encryptionInfoView = [[EncryptionInfoView alloc] initWithDeviceInfo:deviceInfo andMatrixSession:self.mainSession];
[encryptionInfoView onButtonPressed:encryptionInfoView.verifyButton];
encryptionInfoView.delegate = self;
// Add shadow on added view
encryptionInfoView.layer.cornerRadius = 5;
encryptionInfoView.layer.shadowOffset = CGSizeMake(0, 1);
encryptionInfoView.layer.shadowOpacity = 0.5f;
// Add the view and define edge constraints
[rootViewController.view addSubview:encryptionInfoView];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:rootViewController.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:10.0f]];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:rootViewController.bottomLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:-10.0f]];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:rootViewController.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:-10.0f]];
[rootViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:rootViewController.view
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:10.0f]];
[rootViewController.view setNeedsUpdateConstraints];
}
}
- (void)encryptionInfoView:(MXKEncryptionInfoView*)encryptionInfoView didDeviceInfoVerifiedChange:(MXDeviceInfo*)deviceInfo
{
[keyBackupSection reload];
}
#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter
- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow
+12 -18
View File
@@ -816,15 +816,12 @@
self.tabBar.items[index].badgeValue = badgeValue;
if (@available(iOS 10, *))
{
self.tabBar.items[index].badgeColor = badgeColor;
[self.tabBar.items[index] setBadgeTextAttributes:@{
NSForegroundColorAttributeName: ThemeService.shared.theme.baseTextPrimaryColor
}
forState:UIControlStateNormal];
}
self.tabBar.items[index].badgeColor = badgeColor;
[self.tabBar.items[index] setBadgeTextAttributes:@{
NSForegroundColorAttributeName: ThemeService.shared.theme.baseTextPrimaryColor
}
forState:UIControlStateNormal];
}
else
{
@@ -837,16 +834,13 @@
if (mark)
{
self.tabBar.items[index].badgeValue = mark;
self.tabBar.items[index].badgeColor = badgeColor;
if (@available(iOS 10, *))
{
self.tabBar.items[index].badgeColor = badgeColor;
[self.tabBar.items[index] setBadgeTextAttributes:@{
NSForegroundColorAttributeName: ThemeService.shared.theme.baseTextPrimaryColor
}
forState:UIControlStateNormal];
}
[self.tabBar.items[index] setBadgeTextAttributes:@{
NSForegroundColorAttributeName: ThemeService.shared.theme.baseTextPrimaryColor
}
forState:UIControlStateNormal];
}
else
{
@@ -17,9 +17,8 @@
#import <MatrixKit/MatrixKit.h>
#import "DeviceTableViewCell.h"
#import "EncryptionInfoView.h"
@interface UsersDevicesViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource, DeviceTableViewCellDelegate, MXKEncryptionInfoViewDelegate>
@interface UsersDevicesViewController : MXKViewController <UITableViewDelegate, UITableViewDataSource, DeviceTableViewCellDelegate>
/**
Display a map of users/devices.
@@ -20,12 +20,14 @@
#import "AppDelegate.h"
#import "Riot-Swift.h"
@interface UsersDevicesViewController ()
@interface UsersDevicesViewController () <DeviceVerificationCoordinatorBridgePresenterDelegate>
{
MXUsersDevicesMap<MXDeviceInfo*> *usersDevices;
MXSession *mxSession;
void (^onCompleteBlock)(BOOL doneButtonPressed);
DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter;
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
@@ -214,51 +216,10 @@
if (verificationStatus == MXDeviceVerified)
{
// Prompt the user before marking as verified the device.
EncryptionInfoView *encryptionInfoView = [[EncryptionInfoView alloc] initWithDeviceInfo:deviceTableViewCell.deviceInfo andMatrixSession:mxSession];
[encryptionInfoView onButtonPressed:encryptionInfoView.verifyButton];
deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession];
deviceVerificationCoordinatorBridgePresenter.delegate = self;
encryptionInfoView.delegate = self;
// Add shadow on added view
encryptionInfoView.layer.cornerRadius = 5;
encryptionInfoView.layer.shadowOffset = CGSizeMake(0, 1);
encryptionInfoView.layer.shadowOpacity = 0.5f;
// Add the view and define edge constraints
[self.view addSubview:encryptionInfoView];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:10.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.bottomLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:-10.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:-10.0f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:encryptionInfoView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:10.0f]];
[self.view setNeedsUpdateConstraints];
[deviceVerificationCoordinatorBridgePresenter presentFrom:self otherUserId:deviceTableViewCell.deviceInfo.userId otherDeviceId:deviceTableViewCell.deviceInfo.deviceId animated:YES];
}
else
{
@@ -274,17 +235,31 @@
}
}
#pragma mark - MXKEncryptionInfoViewDelegate
#pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate
- (void)encryptionInfoView:(MXKEncryptionInfoView *)encryptionInfoView didDeviceInfoVerifiedChange:(MXDeviceInfo *)deviceInfo
- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId
{
// Update our map
MXDeviceInfo *device = [usersDevices objectForDevice:deviceInfo.deviceId forUser:deviceInfo.userId];
device.verified = deviceInfo.verified;
[deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
deviceVerificationCoordinatorBridgePresenter = nil;
[self.tableView reloadData];
// Update our map
MXWeakify(self);
[mxSession.crypto downloadKeys:@[otherUserId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap) {
MXStrongifyAndReturnIfNil(self);
MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:otherDeviceId forUser:otherUserId];
MXDeviceInfo *device = [self->usersDevices objectForDevice:otherDeviceId forUser:otherUserId];
device.verified = deviceInfo.verified;
[self.tableView reloadData];
} failure:^(NSError *error) {
// Should not happen (the device is in the crypto db)
}];
}
#pragma mark - User actions
- (IBAction)onDone:(id)sender
+4 -2
View File
@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.8.4</string>
<string>0.8.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.8.4</string>
<string>0.8.5</string>
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<key>ITSEncryptionExportComplianceCode</key>
@@ -51,6 +51,8 @@
<string>The photo library is used to send photos and videos.</string>
<key>NSSiriUsageDescription</key>
<string>Siri is used to perform calls even from the lock screen.</string>
<key>NSCalendarsUsageDescription</key>
<string>See your scheduled meetings in the app.</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
@@ -10,3 +10,5 @@
#import "ThemeService.h"
#import "TableViewCellWithCheckBoxAndLabel.h"
#import "RecentsDataSource.h"
#import "AvatarGenerator.h"
#import "EncryptionInfoView.h"
+1 -8
View File
@@ -227,14 +227,7 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/";
self.defaultTextFont = [UIFont systemFontOfSize:15];
self.prefixTextFont = [UIFont boldSystemFontOfSize:15];
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
{
self.bingTextFont = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
}
else
{
self.bingTextFont = [UIFont systemFontOfSize:15];
}
self.bingTextFont = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
self.stateEventTextFont = [UIFont italicSystemFontOfSize:15];
self.callNoticesTextFont = [UIFont italicSystemFontOfSize:15];
self.encryptedMessagesTextFont = [UIFont italicSystemFontOfSize:15];
Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.
Binary file not shown.
@@ -1,18 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty 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 <JitsiMeet/JitsiMeetView.h>
#import <JitsiMeet/JitsiMeetViewDelegate.h>

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