Finish v0.10.4

This commit is contained in:
manuroe 2019-12-11 09:03:48 +01:00
commit 94d9e45d20
70 changed files with 3446 additions and 134 deletions

View file

@ -1,9 +1,40 @@
Changes in 0.10.4 (2019-12-11)
===============================================
Improvements:
* ON/OFF Cross-signing development in a Lab setting (#2855).
Bug fix:
* Device Verification: Stay in infinite waiting (#2878).
Changes in 0.10.3 (2019-12-05)
===============================================
Improvements:
* Upgrade MatrixKit version ([v0.11.3](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.11.3)).
* Integrations: Use the integrations manager provided by the homeserver admin via .well-known (#2815).
* i18n: Add Welsh (cy).
* i18n: Add Italian (it).
* SerializationService: Add deserialisation of Any.
* RiotSharedSettings: New class to handle user settings shared accross Riot apps.
* Widgets: Check user permission before opening a widget (#2833).
* Widgets: Check user permission before opening jitsi (#2842).
* Widgets: Add a contextual menu to refresh, open outside, remove and revoke the permission (#2834).
* Settings: Add an option for disabling use of the integration manager (#2843).
* Jitsi: Display room name, user name and user avatar in the conference screen.
* Improve UNNotificationSound compatibility with MA4 (IMA/ADPCM) file, thanks to @pixlwave (PR #2847).
Bug fix:
* Accessibility: Make checkboxes accessible in terms of service screen.
* RoomVC: Tapping on location links gives 'unable to open link' (#2803).
* RoomVC: Reply to links fail with 'unable to open link' (#2804).
Changes in 0.10.2 (2019-11-15)
===============================================
Bug fix:
* Integrations: Fix terms consent display when they are required.
Changes in 0.10.1 (2019-11-06)
===============================================

View file

@ -7,7 +7,7 @@ use_frameworks!
# Different flavours of pods to MatrixKit
# The current MatrixKit pod version
$matrixKitVersion = '0.11.2'
$matrixKitVersion = '0.11.3'
# The develop branch version
#$matrixKitVersion = 'develop'
@ -64,7 +64,7 @@ abstract_target 'RiotPods' do
pod 'SwiftUTI', :git => 'https://github.com/speramusinc/SwiftUTI.git', :branch => 'master'
# Piwik for analytics
pod 'MatomoTracker', '~> 6.0.1'
pod 'MatomoTracker', '~> 7.2.0'
# Remove warnings from "bad" pods
pod 'OLMKit', :inhibit_warnings => true

View file

@ -46,44 +46,44 @@ PODS:
- JitsiMeetSDK (2.3.1)
- libbase58 (0.1.4)
- libPhoneNumber-iOS (0.9.15)
- MatomoTracker (6.0.1):
- MatomoTracker/Core (= 6.0.1)
- MatomoTracker/Core (6.0.1)
- MatrixKit (0.11.2):
- MatomoTracker (7.2.0):
- MatomoTracker/Core (= 7.2.0)
- MatomoTracker/Core (7.2.0)
- MatrixKit (0.11.3):
- cmark (~> 0.24.1)
- DTCoreText (~> 1.6.21)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixKit/Core (= 0.11.2)
- MatrixSDK (= 0.15.0)
- MatrixKit/Core (= 0.11.3)
- MatrixSDK (= 0.15.2)
- SwiftUTI (~> 1.0.6)
- MatrixKit/AppExtension (0.11.2):
- MatrixKit/AppExtension (0.11.3):
- cmark (~> 0.24.1)
- DTCoreText (~> 1.6.21)
- DTCoreText/Extension
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.15.0)
- MatrixSDK (= 0.15.2)
- SwiftUTI (~> 1.0.6)
- MatrixKit/Core (0.11.2):
- MatrixKit/Core (0.11.3):
- cmark (~> 0.24.1)
- DTCoreText (~> 1.6.21)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.15.0)
- MatrixSDK (= 0.15.2)
- SwiftUTI (~> 1.0.6)
- MatrixSDK (0.15.0):
- MatrixSDK/Core (= 0.15.0)
- MatrixSDK/Core (0.15.0):
- MatrixSDK (0.15.2):
- MatrixSDK/Core (= 0.15.2)
- MatrixSDK/Core (0.15.2):
- AFNetworking (~> 3.2.0)
- GZIP (~> 1.2.2)
- libbase58 (~> 0.1.4)
- OLMKit (~> 3.1.0)
- Realm (~> 3.17.3)
- MatrixSDK/JingleCallStack (0.15.0):
- MatrixSDK/JingleCallStack (0.15.2):
- JitsiMeetSDK (~> 2.3.1)
- MatrixSDK/Core
- MatrixSDK/SwiftSupport (0.15.0):
- MatrixSDK/SwiftSupport (0.15.2):
- MatrixSDK/Core
- OLMKit (3.1.0):
- OLMKit/olmc (= 3.1.0)
@ -107,9 +107,9 @@ DEPENDENCIES:
- cmark
- DGCollectionViewLeftAlignFlowLayout (~> 1.0.4)
- GBDeviceInfo (~> 6.3.0)
- MatomoTracker (~> 6.0.1)
- MatrixKit (= 0.11.2)
- MatrixKit/AppExtension (= 0.11.2)
- MatomoTracker (~> 7.2.0)
- MatrixKit (= 0.11.3)
- MatrixKit/AppExtension (= 0.11.3)
- MatrixSDK/JingleCallStack
- MatrixSDK/SwiftSupport
- OLMKit
@ -164,9 +164,9 @@ SPEC CHECKSUMS:
JitsiMeetSDK: 69e4978fbab21f9a535d1bec3b8d43721a4c72b2
libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd
libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75
MatomoTracker: 3ae4f65a1f5ace8043bda7244888fee28a734de5
MatrixKit: 7f681c9086509a4f5e6e7172ce00f552cba1f8bc
MatrixSDK: 342384d62bac5d95a31a7dab79e5f687bd87ddca
MatomoTracker: 6f89e2561083685a360e223fb663e9ccd57c1d1a
MatrixKit: 5a20025b05490a70694b701e607ec75e0988af21
MatrixSDK: f83bd3c5519c1cb9ac3998f6423574cf528f0250
OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639
Realm: 5a1d9d47bfc101dd597668b1a8af4288a2557f6d
Reusable: 82be188f29d96dc5eff0db7b2393bcc08d2cdd5b
@ -175,6 +175,6 @@ SPEC CHECKSUMS:
SwiftUTI: 917993c124f8eac25e88ced0202fc58d7eb50fa8
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
PODFILE CHECKSUM: 32e1f5dcb15429b0ecff04da8ebce193f145edb5
PODFILE CHECKSUM: 881048fb17d68dd834b18e23929482600daca7f3
COCOAPODS: 1.8.4

View file

@ -70,6 +70,8 @@
3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3275FD8B21A5A2C500B9C13D /* TermsView.swift */; };
3281BCF72201FA4200F4A383 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3281BCF62201FA4200F4A383 /* UIControl.swift */; };
3284A35120A07C210044F922 /* postMessageAPI.js in Resources */ = {isa = PBXBuildFile; fileRef = 3284A35020A07C210044F922 /* postMessageAPI.js */; };
32863A5A2384070300D07C4A /* RiotSharedSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32863A592384070300D07C4A /* RiotSharedSettings.swift */; };
32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */; };
32891D6B2264CBA300C82226 /* SimpleScreenTemplateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D692264CBA300C82226 /* SimpleScreenTemplateViewController.swift */; };
32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D6A2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard */; };
32891D702264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */; };
@ -113,6 +115,7 @@
32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9662270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift */; };
32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9672270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift */; };
32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9682270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift */; };
32FDC1CD2386CD390084717A /* RiotSettingIntegrationProvisioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32FDC1CC2386CD390084717A /* RiotSettingIntegrationProvisioning.swift */; };
3AF393339D2D566CE14AC200 /* Pods_RiotTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */; };
405FD41D306133A48D9B5AA1 /* Pods_RiotPods_Riot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1ACF09217ADF1D7E7A35BC02 /* Pods_RiotPods_Riot.framework */; };
670966FEFE120D865FD8A5B6 /* Pods_RiotPods_SiriIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51187E952D5CECF6D6F5A28E /* Pods_RiotPods_SiriIntents.framework */; };
@ -157,12 +160,15 @@
B110872421F098F0003554A5 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B110872021F098EF003554A5 /* ActivityIndicatorView.xib */; };
B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */; };
B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872221F098F0003554A5 /* ActivityIndicatorView.swift */; };
B11291EA238D35590077B478 /* SlidingModalPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B11291E9238D35590077B478 /* SlidingModalPresentable.swift */; };
B11291EC238D704C0077B478 /* FloatingPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B11291EB238D704C0077B478 /* FloatingPoint.swift */; };
B120863722EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */; };
B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */; };
B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */; };
B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */; };
B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */; };
B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */; };
B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */; };
B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */; };
B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */; };
B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */; };
@ -249,6 +255,12 @@
B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */; };
B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A5B33D227ADF2A004CBA85 /* UIImage.swift */; };
B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A68592229E807800D6C09A /* RoomBubbleCellLayout.swift */; };
B1A6C10723881EF2002882FD /* SlidingModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A6C10623881EF2002882FD /* SlidingModalPresenter.swift */; };
B1A6C109238828A6002882FD /* SlidingModalPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A6C108238828A6002882FD /* SlidingModalPresentationDelegate.swift */; };
B1A6C10B23882B6C002882FD /* SlidingModalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A6C10A23882B6C002882FD /* SlidingModalPresentationAnimator.swift */; };
B1A6C10D23882D1D002882FD /* SlidingModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A6C10C23882D1D002882FD /* SlidingModalPresentationController.swift */; };
B1A6C111238BD236002882FD /* SlidingModalContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A6C110238BD236002882FD /* SlidingModalContainerView.swift */; };
B1A6C113238BD245002882FD /* SlidingModalContainerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1A6C112238BD245002882FD /* SlidingModalContainerView.xib */; };
B1B12B2922942315002CB419 /* UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B12B2822942315002CB419 /* UITouch.swift */; };
B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */; };
B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */; };
@ -501,6 +513,10 @@
B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */; };
B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */; };
B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */; };
B1BD71B5238DCBF700BA92E2 /* SlidingModalEmptyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71B4238DCBF700BA92E2 /* SlidingModalEmptyViewController.swift */; };
B1BD71BC238E8F9600BA92E2 /* WidgetPermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */; };
B1BD71BF238EA56700BA92E2 /* WidgetPermissionViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */; };
B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */; };
B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */; };
B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */; };
B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */; };
@ -579,7 +595,7 @@
F083BD1E1E7009ED00A9B29C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BB0D1E7009EC00A9B29C /* AppDelegate.m */; };
F083BDE61E7009ED00A9B29C /* busy.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDB1E7009EC00A9B29C /* busy.mp3 */; };
F083BDE71E7009ED00A9B29C /* callend.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDC1E7009EC00A9B29C /* callend.mp3 */; };
F083BDE81E7009ED00A9B29C /* message.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDD1E7009EC00A9B29C /* message.mp3 */; };
F083BDE81E7009ED00A9B29C /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDD1E7009EC00A9B29C /* message.caf */; };
F083BDE91E7009ED00A9B29C /* ring.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDE1E7009EC00A9B29C /* ring.mp3 */; };
F083BDEA1E7009ED00A9B29C /* ringback.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F083BBDF1E7009EC00A9B29C /* ringback.mp3 */; };
F083BDED1E7009ED00A9B29C /* MXKRoomBubbleTableViewCell+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BBE61E7009EC00A9B29C /* MXKRoomBubbleTableViewCell+Riot.m */; };
@ -696,6 +712,9 @@
324A204C225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationIncomingCoordinatorType.swift; sourceTree = "<group>"; };
324A204D225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationIncomingViewModelType.swift; sourceTree = "<group>"; };
324A204E225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationIncomingCoordinator.swift; sourceTree = "<group>"; };
325789A5237AB241009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/InfoPlist.strings; sourceTree = "<group>"; };
325789A6237AB27F009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Localizable.strings; sourceTree = "<group>"; };
325789A7237AB297009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Vector.strings; sourceTree = "<group>"; };
3267EFB320E379FD00FF1CAA /* CHANGES.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES.rst; sourceTree = "<group>"; };
3267EFB420E379FD00FF1CAA /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = Podfile; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
3267EFB520E379FD00FF1CAA /* AUTHORS.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AUTHORS.rst; sourceTree = "<group>"; };
@ -703,6 +722,8 @@
3275FD8B21A5A2C500B9C13D /* TermsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsView.swift; sourceTree = "<group>"; };
3281BCF62201FA4200F4A383 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
3284A35020A07C210044F922 /* postMessageAPI.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = postMessageAPI.js; sourceTree = "<group>"; };
32863A592384070300D07C4A /* RiotSharedSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RiotSharedSettings.swift; sourceTree = "<group>"; };
32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiotSettingAllowedWidgets.swift; sourceTree = "<group>"; };
32891D692264CBA300C82226 /* SimpleScreenTemplateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleScreenTemplateViewController.swift; sourceTree = "<group>"; };
32891D6A2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SimpleScreenTemplateViewController.storyboard; sourceTree = "<group>"; };
32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationVerifiedViewController.storyboard; sourceTree = "<group>"; };
@ -758,6 +779,7 @@
32F6B9662270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingCoordinatorType.swift; sourceTree = "<group>"; };
32F6B9672270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewModel.swift; sourceTree = "<group>"; };
32F6B9682270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewModelType.swift; sourceTree = "<group>"; };
32FDC1CC2386CD390084717A /* RiotSettingIntegrationProvisioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiotSettingIntegrationProvisioning.swift; sourceTree = "<group>"; };
3942DD65EBEB7AE647C6392A /* Pods-RiotPods-SiriIntents.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-SiriIntents.debug.xcconfig"; path = "Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents.debug.xcconfig"; sourceTree = "<group>"; };
3D78489021AC9E6400B98A7D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3D78489121AC9E6500B98A7D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -815,12 +837,15 @@
B110872021F098EF003554A5 /* ActivityIndicatorView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ActivityIndicatorView.xib; sourceTree = "<group>"; };
B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = "<group>"; };
B110872221F098F0003554A5 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = "<group>"; };
B11291E9238D35590077B478 /* SlidingModalPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalPresentable.swift; sourceTree = "<group>"; };
B11291EB238D704C0077B478 /* FloatingPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPoint.swift; sourceTree = "<group>"; };
B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryBridgeCoordinatorPresenter.swift; sourceTree = "<group>"; };
B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryTableViewSection.swift; sourceTree = "<group>"; };
B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewModel.swift; sourceTree = "<group>"; };
B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewModelType.swift; sourceTree = "<group>"; };
B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewAction.swift; sourceTree = "<group>"; };
B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewState.swift; sourceTree = "<group>"; };
B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageURLParser.swift; sourceTree = "<group>"; };
B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = "<group>"; };
B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = "<group>"; };
B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = "<group>"; };
@ -956,6 +981,12 @@
B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinator.swift; sourceTree = "<group>"; };
B1A5B33D227ADF2A004CBA85 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
B1A68592229E807800D6C09A /* RoomBubbleCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomBubbleCellLayout.swift; sourceTree = "<group>"; };
B1A6C10623881EF2002882FD /* SlidingModalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalPresenter.swift; sourceTree = "<group>"; };
B1A6C108238828A6002882FD /* SlidingModalPresentationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalPresentationDelegate.swift; sourceTree = "<group>"; };
B1A6C10A23882B6C002882FD /* SlidingModalPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalPresentationAnimator.swift; sourceTree = "<group>"; };
B1A6C10C23882D1D002882FD /* SlidingModalPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalPresentationController.swift; sourceTree = "<group>"; };
B1A6C110238BD236002882FD /* SlidingModalContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalContainerView.swift; sourceTree = "<group>"; };
B1A6C112238BD245002882FD /* SlidingModalContainerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SlidingModalContainerView.xib; sourceTree = "<group>"; };
B1B12B2822942315002CB419 /* UITouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouch.swift; sourceTree = "<group>"; };
B1B5567920EE6C4C00210D55 /* CountryPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryPickerViewController.h; sourceTree = "<group>"; };
B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryPickerViewController.m; sourceTree = "<group>"; };
@ -1344,6 +1375,10 @@
B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewData.swift; sourceTree = "<group>"; };
B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewCell.swift; sourceTree = "<group>"; };
B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionHistoryViewCell.xib; sourceTree = "<group>"; };
B1BD71B4238DCBF700BA92E2 /* SlidingModalEmptyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalEmptyViewController.swift; sourceTree = "<group>"; };
B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewController.swift; sourceTree = "<group>"; };
B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WidgetPermissionViewController.storyboard; sourceTree = "<group>"; };
B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewModel.swift; sourceTree = "<group>"; };
B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPresenter.swift; sourceTree = "<group>"; };
B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorType.swift; sourceTree = "<group>"; };
B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
@ -1369,6 +1404,9 @@
B1C562E0228C7C8C0037F12A /* RoomContextualMenuViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = RoomContextualMenuViewController.storyboard; sourceTree = "<group>"; };
B1C562E6228C7CF10037F12A /* ContextualMenuItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextualMenuItemView.swift; sourceTree = "<group>"; };
B1C562E7228C7CF20037F12A /* ContextualMenuItemView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContextualMenuItemView.xib; sourceTree = "<group>"; };
B1C6FFE723954CE70055347B /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
B1C6FFE823954D3B0055347B /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
B1C6FFE923954D4B0055347B /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Vector.strings; sourceTree = "<group>"; };
B1CA3A2621EF6913000D1D89 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
B1CA3A2821EF692B000D1D89 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutAlertPresenter.swift; sourceTree = "<group>"; };
@ -1426,7 +1464,7 @@
F083BB0D1E7009EC00A9B29C /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
F083BBDB1E7009EC00A9B29C /* busy.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = busy.mp3; sourceTree = "<group>"; };
F083BBDC1E7009EC00A9B29C /* callend.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = callend.mp3; sourceTree = "<group>"; };
F083BBDD1E7009EC00A9B29C /* message.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = message.mp3; sourceTree = "<group>"; };
F083BBDD1E7009EC00A9B29C /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
F083BBDE1E7009EC00A9B29C /* ring.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ring.mp3; sourceTree = "<group>"; };
F083BBDF1E7009EC00A9B29C /* ringback.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringback.mp3; sourceTree = "<group>"; };
F083BBE51E7009EC00A9B29C /* MXKRoomBubbleTableViewCell+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXKRoomBubbleTableViewCell+Riot.h"; sourceTree = "<group>"; };
@ -1682,6 +1720,24 @@
path = Incoming;
sourceTree = "<group>";
};
32863A572384070300D07C4A /* Shared */ = {
isa = PBXGroup;
children = (
32863A582384070300D07C4A /* JSONModels */,
32863A592384070300D07C4A /* RiotSharedSettings.swift */,
);
path = Shared;
sourceTree = "<group>";
};
32863A582384070300D07C4A /* JSONModels */ = {
isa = PBXGroup;
children = (
32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */,
32FDC1CC2386CD390084717A /* RiotSettingIntegrationProvisioning.swift */,
);
path = JSONModels;
sourceTree = "<group>";
};
32891D682264C6A000C82226 /* SimpleScreenTemplate */ = {
isa = PBXGroup;
children = (
@ -1964,6 +2020,16 @@
path = ActivityIndicator;
sourceTree = "<group>";
};
B11291ED238DC8C80077B478 /* WidgetPermission */ = {
isa = PBXGroup;
children = (
B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */,
B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */,
B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */,
);
path = WidgetPermission;
sourceTree = "<group>";
};
B125FE19231D5B5600B72806 /* Discovery */ = {
isa = PBXGroup;
children = (
@ -1977,6 +2043,14 @@
path = Discovery;
sourceTree = "<group>";
};
B12C56ED2396CB0100FAC6DE /* RoomMessageLinkParser */ = {
isa = PBXGroup;
children = (
B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */,
);
path = RoomMessageLinkParser;
sourceTree = "<group>";
};
B14F142522144F6400FA0595 /* RecoveryKey */ = {
isa = PBXGroup;
children = (
@ -2321,9 +2395,25 @@
path = Riot/Modules/Common/CollectionView;
sourceTree = SOURCE_ROOT;
};
B1A6C10523881ECB002882FD /* SlidingModal */ = {
isa = PBXGroup;
children = (
B1A6C10623881EF2002882FD /* SlidingModalPresenter.swift */,
B11291E9238D35590077B478 /* SlidingModalPresentable.swift */,
B1A6C108238828A6002882FD /* SlidingModalPresentationDelegate.swift */,
B1A6C10A23882B6C002882FD /* SlidingModalPresentationAnimator.swift */,
B1A6C10C23882D1D002882FD /* SlidingModalPresentationController.swift */,
B1A6C110238BD236002882FD /* SlidingModalContainerView.swift */,
B1A6C112238BD245002882FD /* SlidingModalContainerView.xib */,
B1BD71B4238DCBF700BA92E2 /* SlidingModalEmptyViewController.swift */,
);
path = SlidingModal;
sourceTree = "<group>";
};
B1B5567620EE6C4C00210D55 /* Modules */ = {
isa = PBXGroup;
children = (
B1A6C10523881ECB002882FD /* SlidingModal */,
32DB556722FDADE50016329E /* ServiceTerms */,
3232AB94225730E100AD6A5C /* DeviceVerification */,
B1B556EA20EE6C4C00210D55 /* Main */,
@ -2824,6 +2914,7 @@
children = (
B1B5576420EE702800210D55 /* IntegrationManagerViewController.h */,
B1B5576020EE702800210D55 /* IntegrationManagerViewController.m */,
B11291ED238DC8C80077B478 /* WidgetPermission */,
B1B5576120EE702800210D55 /* WidgetPicker */,
B1B5576520EE702800210D55 /* Widgets */,
);
@ -3378,6 +3469,7 @@
B1B5597C20EFC3DF00210D55 /* Managers */ = {
isa = PBXGroup;
children = (
B12C56ED2396CB0100FAC6DE /* RoomMessageLinkParser */,
B1B9DED822E9B7120065E677 /* Serialization */,
B1FDF56321F68C0700BA3834 /* PasswordStrength */,
B1798300211B137B001FD722 /* OnBoarding */,
@ -3414,6 +3506,7 @@
B1B5598A20EFC42100210D55 /* Settings */ = {
isa = PBXGroup;
children = (
32863A572384070300D07C4A /* Shared */,
B1B5597F20EFC3DF00210D55 /* RiotSettings.swift */,
);
path = Settings;
@ -3646,7 +3739,7 @@
children = (
F083BBDB1E7009EC00A9B29C /* busy.mp3 */,
F083BBDC1E7009EC00A9B29C /* callend.mp3 */,
F083BBDD1E7009EC00A9B29C /* message.mp3 */,
F083BBDD1E7009EC00A9B29C /* message.caf */,
F083BBDE1E7009EC00A9B29C /* ring.mp3 */,
F083BBDF1E7009EC00A9B29C /* ringback.mp3 */,
);
@ -3684,6 +3777,7 @@
B1C562CB228AB3510037F12A /* UIStackView.swift */,
B1B12B2822942315002CB419 /* UITouch.swift */,
B1DCC63322E72C1B00625807 /* UISearchBar.swift */,
B11291EB238D704C0077B478 /* FloatingPoint.swift */,
);
path = Categories;
sourceTree = "<group>";
@ -3913,6 +4007,8 @@
ja,
hu,
pl,
cy,
it,
);
mainGroup = F094A9991B78D8F000B1FBBF;
productRefGroup = F094A9A31B78D8F000B1FBBF /* Products */;
@ -3962,6 +4058,7 @@
B1B5592B20EF7A5D00210D55 /* TableViewCellWithButton.xib in Resources */,
B1B5574620EE6C4D00210D55 /* StartChatViewController.xib in Resources */,
B1B5590A20EF768F00210D55 /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib in Resources */,
B1A6C113238BD245002882FD /* SlidingModalContainerView.xib in Resources */,
B1B5594420EF7BD000210D55 /* TableViewCellWithCollectionView.xib in Resources */,
B1B558D520EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */,
B1B5590020EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib in Resources */,
@ -4015,7 +4112,7 @@
B1B558EC20EF768F00210D55 /* RoomMembershipCollapsedBubbleCell.xib in Resources */,
B1B558D720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */,
B1B5590820EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.xib in Resources */,
F083BDE81E7009ED00A9B29C /* message.mp3 in Resources */,
F083BDE81E7009ED00A9B29C /* message.caf in Resources */,
B1107ECA2200B09F0038014B /* KeyBackupRecoverSuccessViewController.storyboard in Resources */,
B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */,
B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */,
@ -4045,6 +4142,7 @@
3232AB4B2256558300AD6A5C /* TemplateScreenViewController.storyboard in Resources */,
B1D1BDA822BBAFC900831367 /* ReactionsMenuView.xib in Resources */,
32B1FEDB21A46F2C00637127 /* TermsView.xib in Resources */,
B1BD71BF238EA56700BA92E2 /* WidgetPermissionViewController.storyboard in Resources */,
B1DCC63222E7026F00625807 /* EmojiPickerHeaderView.xib in Resources */,
B1B5578E20EF568D00210D55 /* GroupInviteTableViewCell.xib in Resources */,
B1B5582020EF625800210D55 /* SimpleRoomTitleView.xib in Resources */,
@ -4345,6 +4443,7 @@
B1B558DE20EF768F00210D55 /* RoomIncomingAttachmentBubbleCell.m in Sources */,
B1B5574820EE6C4D00210D55 /* PeopleViewController.m in Sources */,
B1B5598720EFC3E000210D55 /* Widget.m in Sources */,
B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */,
B1B557E320EF60B900210D55 /* MessagesSearchResultAttachmentBubbleCell.m in Sources */,
B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */,
3232ABAA225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift in Sources */,
@ -4375,7 +4474,9 @@
B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */,
B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */,
B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */,
B1BD71BC238E8F9600BA92E2 /* WidgetPermissionViewController.swift in Sources */,
B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
B11291EA238D35590077B478 /* SlidingModalPresentable.swift in Sources */,
B157FAA823264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift in Sources */,
B169330B20F3CA3A00746532 /* Contact.m in Sources */,
B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */,
@ -4396,6 +4497,7 @@
32A6001E22C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift in Sources */,
B1B5574A20EE6C4D00210D55 /* MediaPickerViewController.m in Sources */,
B1B5598520EFC3E000210D55 /* RageShakeManager.m in Sources */,
B1A6C111238BD236002882FD /* SlidingModalContainerView.swift in Sources */,
B1DCC62D22E61EAF00625807 /* EmojiPickerViewCell.swift in Sources */,
3232ABA8225730E100AD6A5C /* DeviceVerificationStartViewState.swift in Sources */,
B1B558D420EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
@ -4409,8 +4511,10 @@
B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */,
32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */,
B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */,
32863A5A2384070300D07C4A /* RiotSharedSettings.swift in Sources */,
B1B5594720EF7BD000210D55 /* RoomCollectionViewCell.m in Sources */,
B10CFBC32268D99D00A5842E /* JitsiService.swift in Sources */,
B1A6C10B23882B6C002882FD /* SlidingModalPresentationAnimator.swift in Sources */,
B1B558C120EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */,
B1B5573E20EE6C4D00210D55 /* RiotNavigationController.m in Sources */,
B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */,
@ -4433,6 +4537,7 @@
3232ABBC2257BE6500AD6A5C /* DeviceVerificationVerifyViewAction.swift in Sources */,
F05927C91FDED836009F2A68 /* MXGroup+Riot.m in Sources */,
B1B5594520EF7BD000210D55 /* TableViewCellWithCollectionView.m in Sources */,
B1A6C109238828A6002882FD /* SlidingModalPresentationDelegate.swift in Sources */,
32DB557722FDADE50016329E /* ServiceTermsModalCoordinator.swift in Sources */,
32DB557922FDADE50016329E /* ServiceTermsModalScreenViewModel.swift in Sources */,
32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */,
@ -4483,6 +4588,7 @@
B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */,
B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */,
B1C45A8B232A8C2600165425 /* SettingsIdentityServerViewModel.swift in Sources */,
B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */,
B1C45A86232A8C2600165425 /* SettingsIdentityServerViewModelType.swift in Sources */,
F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */,
B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */,
@ -4510,6 +4616,7 @@
B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */,
B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */,
B1B5574F20EE6C4D00210D55 /* RoomsViewController.m in Sources */,
32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */,
B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */,
B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */,
B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */,
@ -4539,6 +4646,7 @@
B1B5572620EE6C4D00210D55 /* RoomFilesSearchViewController.m in Sources */,
B1B5583120EF66BA00210D55 /* RoomIdOrAliasTableViewCell.m in Sources */,
B1CA3A2921EF692B000D1D89 /* UIView.swift in Sources */,
B1A6C10D23882D1D002882FD /* SlidingModalPresentationController.swift in Sources */,
32A6001D22C661100042C1D9 /* EditHistoryCoordinatorType.swift in Sources */,
F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */,
B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */,
@ -4552,6 +4660,7 @@
B1B5574120EE6C4D00210D55 /* RecentsViewController.m in Sources */,
B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */,
B1B5577120EE702800210D55 /* StickerPickerViewController.m in Sources */,
32FDC1CD2386CD390084717A /* RiotSettingIntegrationProvisioning.swift in Sources */,
B104C2942203773C00D9F496 /* KeyBackupBannerPreferences.swift in Sources */,
B1B5572020EE6C4D00210D55 /* ContactsTableViewController.m in Sources */,
B1B5581920EF625800210D55 /* RoomTitleView.m in Sources */,
@ -4566,6 +4675,7 @@
B1B5597520EFB02A00210D55 /* InviteRecentTableViewCell.m in Sources */,
B1B5571E20EE6C4D00210D55 /* ContactDetailsViewController.m in Sources */,
B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */,
B1A6C10723881EF2002882FD /* SlidingModalPresenter.swift in Sources */,
B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */,
B10B3B5B2201DD740072C76B /* KeyBackupBannerCell.swift in Sources */,
B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */,
@ -4629,6 +4739,7 @@
B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */,
B1B5578620EF564900210D55 /* GroupTableViewCellWithSwitch.m in Sources */,
B1098BE821ECFE52000DDA48 /* Coordinator.swift in Sources */,
B11291EC238D704C0077B478 /* FloatingPoint.swift in Sources */,
B1B557E920EF60F500210D55 /* MessagesSearchResultTextMsgBubbleCell.m in Sources */,
324A2050225FC571004FE8B0 /* DeviceVerificationIncomingViewController.swift in Sources */,
B1098C0D21ED07E4000DDA48 /* NavigationRouter.swift in Sources */,
@ -4683,6 +4794,7 @@
3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */,
B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */,
B1B558F820EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
B1BD71B5238DCBF700BA92E2 /* SlidingModalEmptyViewController.swift in Sources */,
B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */,
32242F0921E8B05F00725742 /* UIColor.swift in Sources */,
B16932E720F3C37100746532 /* HomeMessagesSearchDataSource.m in Sources */,
@ -4756,6 +4868,8 @@
3D78489221AC9E6500B98A7D /* ja */,
3D78489521ACA25300B98A7D /* hu */,
32DAF8DD231813E100654A44 /* pl */,
325789A7237AB297009388E6 /* cy */,
B1C6FFE923954D4B0055347B /* it */,
);
name = Vector.strings;
sourceTree = "<group>";
@ -4779,6 +4893,8 @@
3D78489021AC9E6400B98A7D /* ja */,
3D78489321ACA25200B98A7D /* hu */,
32DAF8DB231813C800654A44 /* pl */,
325789A5237AB241009388E6 /* cy */,
B1C6FFE723954CE70055347B /* it */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
@ -4802,6 +4918,8 @@
3D78489121AC9E6500B98A7D /* ja */,
3D78489421ACA25300B98A7D /* hu */,
32DAF8DC231813D500654A44 /* pl */,
325789A6237AB27F009388E6 /* cy */,
B1C6FFE823954D3B0055347B /* it */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -4829,6 +4947,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9C3242E3FE95BCDA9562C75D /* Pods-RiotPods-RiotShareExtension.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
APPLICATION_EXTENSION_API_ONLY = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@ -4868,6 +4987,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 4FC6A5D63FAD1B27C2F57AFA /* Pods-RiotPods-RiotShareExtension.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
APPLICATION_EXTENSION_API_ONLY = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@ -4908,6 +5028,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 3942DD65EBEB7AE647C6392A /* Pods-RiotPods-SiriIntents.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
@ -4940,6 +5061,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E2599D0ECB8DD206624E450B /* Pods-RiotPods-SiriIntents.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
@ -5079,6 +5201,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 43C2962BE367F59220F517FA /* Pods-RiotPods-Riot.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Riot/SupportingFiles/Riot.entitlements;
@ -5112,6 +5235,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = B43DC75D1590BB8A4243BD4D /* Pods-RiotPods-Riot.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Riot/SupportingFiles/Riot.entitlements;
@ -5143,7 +5267,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = BABB6681FBD79219B1213D6C /* Pods-RiotTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
DEVELOPMENT_TEAM = 7J4U792NQT;
@ -5174,7 +5298,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = AC34BF67FD21A9D01C16AE5D /* Pods-RiotTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
DEVELOPMENT_TEAM = 7J4U792NQT;

View file

@ -238,6 +238,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
@property (weak, nonatomic) UIViewController *gdprConsentController;
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter;
/**
Used to manage on boarding steps, like create DM with riot bot
@ -374,7 +375,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
{
// Create message sound
NSURL *messageSoundURL = [[NSBundle mainBundle] URLForResource:@"message" withExtension:@"mp3"];
NSURL *messageSoundURL = [[NSBundle mainBundle] URLForResource:@"message" withExtension:@"caf"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)messageSoundURL, &_messageSound);
NSLog(@"[AppDelegate] willFinishLaunchingWithOptions: Done");
@ -1565,7 +1566,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
soundName = action.parameters[@"value"];
if ([soundName isEqualToString:@"default"])
{
soundName = @"message.mp3";
soundName = @"message.caf";
}
}
}
@ -4099,18 +4100,27 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
{
if (!_jitsiViewController && !currentCallViewController)
{
_jitsiViewController = [JitsiViewController jitsiViewController];
MXWeakify(self);
[self checkPermissionForNativeWidget:jitsiWidget fromUrl:JitsiService.shared.serverURL completion:^(BOOL granted) {
MXStrongifyAndReturnIfNil(self);
if (!granted)
{
return;
}
[_jitsiViewController openWidget:jitsiWidget withVideo:video success:^{
self->_jitsiViewController = [JitsiViewController jitsiViewController];
_jitsiViewController.delegate = self;
[self presentJitsiViewController:nil];
} failure:^(NSError *error) {
[self->_jitsiViewController openWidget:jitsiWidget withVideo:video success:^{
_jitsiViewController = nil;
self->_jitsiViewController.delegate = self;
[self presentJitsiViewController:nil];
[self showAlertWithTitle:nil message:NSLocalizedStringFromTable(@"call_jitsi_error", @"Vector", nil)];
} failure:^(NSError *error) {
self->_jitsiViewController = nil;
[self showAlertWithTitle:nil message:NSLocalizedStringFromTable(@"call_jitsi_error", @"Vector", nil)];
}];
}];
}
else
@ -4166,6 +4176,123 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
}
#pragma mark - Native Widget Permission
- (void)checkPermissionForNativeWidget:(Widget*)widget fromUrl:(NSURL*)url completion:(void (^)(BOOL granted))completion
{
MXSession *session = widget.mxSession;
if ([widget.widgetEvent.sender isEqualToString:session.myUser.userId])
{
// No need of more permission check if the user created the widget
completion(YES);
return;
}
// Check permission in user Riot settings
__block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session];
WidgetPermission permission = [sharedSettings permissionForNative:widget fromUrl:url];
if (permission == WidgetPermissionGranted)
{
completion(YES);
}
else
{
// Note: ask permission again if the user previously declined it
[self askNativeWidgetPermissionWithWidget:widget completion:^(BOOL granted) {
// Update the settings in user account data in parallel
[sharedSettings setPermission:granted ? WidgetPermissionGranted : WidgetPermissionDeclined
forNative:widget fromUrl:url
success:^
{
sharedSettings = nil;
}
failure:^(NSError * _Nullable error)
{
NSLog(@"[WidgetVC] setPermissionForWidget failed. Error: %@", error);
sharedSettings = nil;
}];
completion(granted);
}];
}
}
- (void)askNativeWidgetPermissionWithWidget:(Widget*)widget completion:(void (^)(BOOL granted))completion
{
if (!self.slidingModalPresenter)
{
self.slidingModalPresenter = [SlidingModalPresenter new];
}
[self.slidingModalPresenter dismissWithAnimated:NO completion:nil];
NSString *widgetCreatorUserId = widget.widgetEvent.sender ?: NSLocalizedStringFromTable(@"room_participants_unknown", @"Vector", nil);
MXSession *session = widget.mxSession;
MXRoom *room = [session roomWithRoomId:widget.widgetEvent.roomId];
MXRoomState *roomState = room.dangerousSyncState;
MXRoomMember *widgetCreatorRoomMember = [roomState.members memberWithUserId:widgetCreatorUserId];
NSString *widgetDomain = @"";
if (widget.url)
{
NSString *host = [[NSURL alloc] initWithString:widget.url].host;
if (host)
{
widgetDomain = host;
}
}
MXMediaManager *mediaManager = widget.mxSession.mediaManager;
NSString *widgetCreatorDisplayName = widgetCreatorRoomMember.displayname;
NSString *widgetCreatorAvatarURL = widgetCreatorRoomMember.avatarUrl;
NSArray<NSString*> *permissionStrings = @[
NSLocalizedStringFromTable(@"room_widget_permission_display_name_permission", @"Vector", nil),
NSLocalizedStringFromTable(@"room_widget_permission_avatar_url_permission", @"Vector", nil)
];
WidgetPermissionViewModel *widgetPermissionViewModel = [[WidgetPermissionViewModel alloc] initWithCreatorUserId:widgetCreatorUserId
creatorDisplayName:widgetCreatorDisplayName creatorAvatarUrl:widgetCreatorAvatarURL widgetDomain:widgetDomain
isWebviewWidget:NO
widgetPermissions:permissionStrings
mediaManager:mediaManager];
WidgetPermissionViewController *widgetPermissionViewController = [WidgetPermissionViewController instantiateWith:widgetPermissionViewModel];
MXWeakify(self);
widgetPermissionViewController.didTapContinueButton = ^{
MXStrongifyAndReturnIfNil(self);
[self.slidingModalPresenter dismissWithAnimated:YES completion:^{
completion(YES);
}];
};
widgetPermissionViewController.didTapCloseButton = ^{
MXStrongifyAndReturnIfNil(self);
[self.slidingModalPresenter dismissWithAnimated:YES completion:^{
completion(NO);
}];
};
UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController;
[self.slidingModalPresenter present:widgetPermissionViewController
from:presentingViewController
animated:YES
completion:nil];
}
#pragma mark - Call status handling
- (void)addCallStatusBar:(NSString*)buttonTitle

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Binary file not shown.

View file

@ -825,8 +825,8 @@
"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Не е конфигуриран сървър за самоличност, така че не можете да започнете чат с контакт посредством имейл адрес.";
// Service terms
"service_terms_modal_title" = "Условия за ползване";
"service_terms_modal_message" = "За да продължите е необходимо да приемете Условията за ползване.";
"service_terms_modal_accept_button" = "Приемам";
"service_terms_modal_message" = "За да продължите, трябва да приемете Условията за ползване на тази услуга (%@).";
"service_terms_modal_accept_button" = "Приеми";
"service_terms_modal_description_for_identity_server" = "Бъдете откриваеми от потребители";
"service_terms_modal_description_for_integration_manager" = "Използвайте ботове, връзки към други мрежи и стикери";
"room_participants_remove_third_party_invite_prompt_msg" = "Сигурни ли сте, че искате да оттеглите тази покана?";
@ -867,3 +867,59 @@
"settings_devices_description" = "Публичното име на устройството е видимо за хората, с които общувате";
"settings_discovery_no_identity_server" = "В момента не използвате сървър за самоличност. Добавете такъв, за да бъдете откриваеми от съществуващи познати контакти.";
"settings_discovery_terms_not_signed" = "Съгласете се с условията за ползване на сървъра за самоличност (%@) за да бъдете откриваеми по имейл адрес или телефон.";
"settings_discovery_three_pids_management_information_part1" = "Управлявайте кои имейл адреси и телефонни номера други потребители могат да използват за да ви открият или поканят в стаи. Добавяйте и премахвайте имейл адреси и телефонни номера от този списък в ";
"settings_discovery_three_pids_management_information_part2" = "Потребителски настройки";
"settings_discovery_three_pids_management_information_part3" = ".";
"settings_discovery_error_message" = "Възникна грешка. Опитайте пак.";
"settings_discovery_three_pid_details_title_email" = "Управление на имейл";
"settings_discovery_three_pid_details_information_email" = "Управлявайте настройките за този имейл адрес, който други потребители могат да използват за да ви открият или поканят в стая. Добавяйте и премахвайте имейл адреси в Профил.";
"settings_discovery_three_pid_details_title_phone_number" = "Управлявай телефонен номер";
"settings_discovery_three_pid_details_information_phone_number" = "Управлявайте настройките за този телефонен номер, който други потребители могат да използват за да ви открият или поканят в стая. Добавяйте и премахвайте телефонни номера в Профил.";
"settings_discovery_three_pid_details_share_action" = "Сподели";
"settings_discovery_three_pid_details_revoke_action" = "Оттегли";
"settings_discovery_three_pid_details_cancel_email_validation_action" = "Откажи потвърждението на имейл";
"settings_discovery_three_pid_details_enter_sms_code_action" = "Въведи SMS код за активация";
"settings_identity_server_description" = "Използвайки сървъра за самоличност по-горе, може да откривате и да бъдете открити от съществуващи познати ваши контакти.";
"settings_identity_server_no_is" = "Не е настроен сървър за самоличност";
"settings_identity_server_no_is_description" = "В момента не използвате сървър за самоличност. Добавете такъв по-горе, за да откривате и бъдете откриваеми от съществуващи ваши контакти.";
// Identity server settings
"identity_server_settings_title" = "Сървър за самоличност";
"identity_server_settings_description" = "В момента използвате %2 за да откривате и да бъдете открити от съществуващи ваши контакти.";
"identity_server_settings_no_is_description" = "В момента не използвате сървър за самоличност. Добавете такъв по-горе, за да откривате и бъдете открити от съществуващи ваши контакти.";
"identity_server_settings_place_holder" = "Въведете сървър за самоличност";
"identity_server_settings_add" = "Добави";
"identity_server_settings_change" = "Промени";
"identity_server_settings_disconnect_info" = "Ако прекъснете връзката със сървъра за самоличност, няма да можете да бъдете открити от други потребители, както и да ви канят в стаи по имейл или телефонен номер.";
"identity_server_settings_disconnect" = "Прекъсни";
"identity_server_settings_alert_no_terms_title" = "Сървъра за самоличност няма условия за ползване";
"identity_server_settings_alert_no_terms" = "Сървъра за самоличност, който сте избрали няма условия за ползване на услугата. Продължете, само ако вярвате на собственика на сървъра.";
"identity_server_settings_alert_change_title" = "Промяна на сървър за самоличност";
"identity_server_settings_alert_change" = "Прекъсване от сървър за самоличност %1$@ и вместо това свързване с %2$@?";
"identity_server_settings_alert_disconnect_title" = "Прекъсване на връзка със сървър за самоличност";
"identity_server_settings_alert_disconnect" = "Прекъсване на връзката със сървър за самоличност %@?";
"identity_server_settings_alert_disconnect_button" = "Прекъсни";
"identity_server_settings_alert_disconnect_still_sharing_3pid" = "Личните ви данни все още са споделени със сървър за самоличност %@.\n\nПрепоръчваме да премахнете имейл адресите и телефонните номера от сървъра за самоличност преди прекъсване на връзката.";
"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Прекъсни въпреки това";
"identity_server_settings_alert_error_terms_not_accepted" = "Трябва да приемете условията на %@ за да го използвате за сървър за самоличност.";
"identity_server_settings_alert_error_invalid_identity_server" = "%@ не е валиден сървър за самоличност.";
"call_no_stun_server_error_title" = "Обаждането се провали поради грешно конфигуриран сървър";
"call_no_stun_server_error_message_1" = "Попитайте администратора на сървъра %@ да конфигурира TURN сървър за да може разговорите да работят надеждно.";
"call_no_stun_server_error_message_2" = "Като алтернатива, също може да използвате публичния сървър %@, но това няма да е толкова надеждно, а и ще сподели IP адреса ви със сървъра. Може да управлявате това в Настройки";
"call_no_stun_server_error_use_fallback_button" = "Опитай с %@";
"service_terms_modal_decline_button" = "Откажи";
"service_terms_modal_description_for_identity_server_1" = "Открийте други по телефон или имейл";
"service_terms_modal_description_for_identity_server_2" = "Бъдете откриваеми по телефон или имейл";
// Service terms - Variant for identity server when displayed out of a context
"service_terms_modal_title_identity_server" = "Откриване на контакти";
"service_terms_modal_message_identity_server" = "Приемете условията на сървъра за самоличност (%@) за да откривате контакти.";
// Generic errors
"error_invite_3pid_with_no_identity_server" = "Добавете сървър за самоличност в настройки за да каните по имейл.";
"error_not_supported_on_mobile" = "Не може да правите това от %@ мобилен телефон.";
"settings_integrations" = "ИНТЕГРАЦИИ";
"settings_integrations_allow_button" = "Управлявай интеграциите";
"settings_integrations_allow_description" = "Използвайте мениджър на интеграции (%@) за да управлявате ботове, мостове към други мрежи, приспособления и стикери.\n\nМениджърите на интеграции получават данни за конфигурация, могат да модифицират приспособления, да пращат покани в стаи и да контролират нивата на достъп вместо вас.";
"widget_menu_refresh" = "Опресни";
"widget_menu_open_outside" = "Отвори в браузър";
"widget_menu_revoke_permission" = "Премахни достъпа за мен";
"widget_menu_remove" = "Премахни за всички";
"widget_integration_manager_disabled" = "Необходимо е да включите мениджър на интеграции от настройки";

View file

@ -0,0 +1,6 @@
// Permissions usage explanations
"NSCameraUsageDescription" = "Defnyddir y camera i dynnu lluniau a fideos, gwneud galwadau fideo.";
"NSPhotoLibraryUsageDescription" = "Defnyddir y llyfrgell ffotograffau i anfon lluniau a fideos.";
"NSMicrophoneUsageDescription" = "Defnyddir y meicroffon i gymryd fideos, gwneud galwadau.";
"NSContactsUsageDescription" = "I ddarganfod cysylltiadau sydd eisoes yn defnyddio Matrix, gall Riot anfon cyfeiriadau e-bost a rhifau ffôn yn eich llyfr cyfeiriadau at y gweinydd adnabod Matrix o'ch dewis. Pan gânt eu cefnogi, mae data personol yn cael ei amgodio cyn ei anfon - gwiriwch bolisi preifatrwydd eich gweinydd adnabod i gael mwy o fanylion.";
"NSCalendarsUsageDescription" = "Gweler eich cyfarfodydd a drefnwyd yn yr ap.";

View file

@ -0,0 +1,56 @@
/* Message title for a specific person in a named room */
"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ yn %@";
/* New message from a specific person, not referencing a room */
"MSG_FROM_USER" = "Anfonwyd %@ neges";
/* New message from a specific person in a named room */
"MSG_FROM_USER_IN_ROOM" = "Postiodd %@ yn %@";
/* New message from a specific person, not referencing a room. Content included. */
"MSG_FROM_USER_WITH_CONTENT" = "%@: %@";
/* New message from a specific person in a named room. Content included. */
"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ yn %@: %@";
/* New action message from a specific person, not referencing a room. */
"ACTION_FROM_USER" = "* %@ %@";
/* New action message from a specific person in a named room. */
"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@";
/* New action message from a specific person, not referencing a room. */
"IMAGE_FROM_USER" = "Anfonwyd %@ lun %@";
/* New action message from a specific person in a named room. */
"IMAGE_FROM_USER_IN_ROOM" = "Postiodd %@ lun %@ yn %@";
/* A single unread message in a room */
"SINGLE_UNREAD_IN_ROOM" = "Cawsoch neges yn %@";
/* A single unread message */
"SINGLE_UNREAD" = "Cawsoch neges";
/* Sticker from a specific person, not referencing a room. */
"STICKER_FROM_USER" = "Anfonodd %@ sticer";
/* Multiple unread messages in a room */
"UNREAD_IN_ROOM" = "%@ neges newydd yn %@";
/* Multiple unread messages from a specific person, not referencing a room */
"MSGS_FROM_USER" = "%@ neges newydd yn %@";
/* Multiple unread messages from two people */
"MSGS_FROM_TWO_USERS" = "%@ neges newydd gan %@ a %@";
/* Multiple unread messages from three people */
"MSGS_FROM_THREE_USERS" = "%@ neges newydd gan %@, %@ a %@";
/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */
"MSGS_FROM_TWO_PLUS_USERS" = "%@ neges newydd gan %@, %@ ac eraill";
/* Multiple messages in two rooms */
"MSGS_IN_TWO_ROOMS" = "%@ neges newydd yn %@ a %@";
/* Look, stuff's happened, alright? Just open the app. */
"MSGS_IN_TWO_PLUS_ROOMS" = "%@ neges newydd yn %@, %@ ac eraill";
/* A user has invited you to a chat */
"USER_INVITE_TO_CHAT" = "Mae %@ wedi eich gwahodd chi i sgwrsio";
/* A user has invited you to an (unamed) group chat */
"USER_INVITE_TO_CHAT_GROUP_CHAT" = "Mae %@ wedi eich gwahodd chi i sgwrsio mewn grŵp";
/* A user has invited you to a named room */
"USER_INVITE_TO_NAMED_ROOM" = "Mae %@ wedi eich gwahodd chi i %@";
/* Incoming one-to-one voice call */
"VOICE_CALL_FROM_USER" = "Galwad gan %@";
/* Incoming one-to-one video call */
"VIDEO_CALL_FROM_USER" = "Galwad fideo gan %@";
/* Incoming unnamed voice conference invite from a specific person */
"VOICE_CONF_FROM_USER" = "Galwad grŵp gan %@";
/* Incoming unnamed video conference invite from a specific person */
"VIDEO_CONF_FROM_USER" = "Galwad fideo grŵp gan %@";
/* Incoming named voice conference invite from a specific person */
"VOICE_CONF_NAMED_FROM_USER" = "Galwad grŵp gan %@: '%@'";
/* Incoming named video conference invite from a specific person */
"VIDEO_CONF_NAMED_FROM_USER" = "Galwad fideo grŵp gan %@: '%@'";

View file

@ -172,7 +172,7 @@
// Contacts
"contacts_address_book_section" = "CYSYLLTIADAU LLEOL";
"contacts_address_book_matrix_users_toggle" = "Defnyddwyr Matrix yn unig";
"contacts_address_book_no_identity_server" = "Dim gweinyddwr adnabod wedi'i osod";
"contacts_address_book_no_identity_server" = "Dim gweinydd adnabod wedi'i osod";
"contacts_address_book_no_contact" = "Dim cysylltiadau lleol";
"contacts_address_book_permission_required" = "Mae angen caniatâd i gael mynediad at gysylltiadau lleol";
"contacts_address_book_permission_denied" = "Gwrthodwyd caniatâd i Riot gael mynediad i'ch cysylltiadau lleol";
@ -425,23 +425,23 @@
"settings_key_backup_info" = "Sicrheir negeseuon wedi'u hamgryptio gydag amgryptio o'r dechrau i'r diwedd. Dim ond chi a'r derbynnydd / derbynwyr sydd â'r allweddi i ddarllen y negeseuon hyn.";
"settings_key_backup_info_checking" = "Gwirio...";
"settings_key_backup_info_none" = "Nid yw'ch allweddi yn cael eu cadw wrth gefn o'r ddyfais hon.";
"settings_key_backup_info_signout_warning" = "Cysylltwch y ddyfais hon â Key Backup cyn arwyddo allan er mwyn osgoi colli unrhyw allweddi a allai fod ar y ddyfais hon yn unig.";
"settings_key_backup_info_version" = "Fersiwn Key Backup: %@";
"settings_key_backup_info_signout_warning" = "Cysylltwch y ddyfais hon â Allweddi Wrth Gefn cyn arwyddo allan er mwyn osgoi colli unrhyw allweddi a allai fod ar y ddyfais hon yn unig.";
"settings_key_backup_info_version" = "Fersiwn Allweddi Wrth Gefn: %@";
"settings_key_backup_info_algorithm" = "Algorithm: %@";
"settings_key_backup_info_valid" = "Mae'r ddyfais hon yn gwneud copi wrth gefn o'ch allweddi.";
"settings_key_backup_info_not_valid" = "Nid yw'r ddyfais hon yn gwneud copi wrth gefn o'ch allweddi, ond mae gennych gopi wrth gefn y gallwch ei adfer ac ychwanegu ato wrth symud ymlaen.";
"settings_key_backup_info_progress" = "Creu copi wrth gefn o allweddi %@...";
"settings_key_backup_info_progress_done" = "Pob allwedd â copi wrth gefn";
"settings_key_backup_info_trust_signature_unknown" = "Mae gan Key Backup lofnod o'r ddyfais gydag ID: %@";
"settings_key_backup_info_trust_signature_valid" = "Mae gan Key Backup lofnod dilys o'r ddyfais hon";
"settings_key_backup_info_trust_signature_valid_device_verified" = "Mae gan Key Backup lofnod dilys o %@";
"settings_key_backup_info_trust_signature_valid_device_unverified" = "Mae gan Key Backup lofnod o %@";
"settings_key_backup_info_trust_signature_invalid_device_verified" = "Mae gan Key Backup lofnod annilys o %@";
"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Mae gan Key Backup lofnod annilys o %@";
"settings_key_backup_button_create" = "Dechrau defnyddio Key Backup";
"settings_key_backup_info_trust_signature_unknown" = "Mae gan Allweddi Wrth Gefn lofnod o'r ddyfais gydag ID: %@";
"settings_key_backup_info_trust_signature_valid" = "Mae gan Allweddi Wrth Gefn lofnod dilys o'r ddyfais hon";
"settings_key_backup_info_trust_signature_valid_device_verified" = "Mae gan Allweddi Wrth Gefn lofnod dilys o %@";
"settings_key_backup_info_trust_signature_valid_device_unverified" = "Mae gan Allweddi Wrth Gefn lofnod o %@";
"settings_key_backup_info_trust_signature_invalid_device_verified" = "Mae gan Allweddi Wrth Gefn lofnod annilys o %@";
"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Mae gan Allweddi Wrth Gefn lofnod annilys o %@";
"settings_key_backup_button_create" = "Dechrau defnyddio Allweddi Wrth Gefn";
"settings_key_backup_button_restore" = "Adfer o'r copi wrth gefn";
"settings_key_backup_button_delete" = "Dileu copi wrth gefn";
"settings_key_backup_button_connect" = "Cysylltwch y ddyfais hon i Key Backup";
"settings_key_backup_button_connect" = "Cysylltwch y ddyfais hon i Allweddi Wrth Gefn";
"settings_key_backup_delete_confirmation_prompt_title" = "Dileu copi wrth gefn";
"settings_key_backup_delete_confirmation_prompt_msg" = "Ydych chi'n siwr? Byddwch yn colli'ch negeseuon wedi'u hamgryptio os nad yw'ch allweddi wedi'u cadw wrth gefn yn gywir.";
"settings_devices_description" = "Mae enw cyhoeddus dyfais yn weladwy i'r bobl rydych chi'n cyfathrebu â nhw";
@ -460,7 +460,7 @@
"settings_discovery_three_pid_details_cancel_email_validation_action" = "Canslo gwiro e-bost";
"settings_discovery_three_pid_details_enter_sms_code_action" = "Rhowch côd actifadu neges destyn";
"settings_identity_server_description" = "Gan ddefnyddio'r gweinydd adnabod a osodir uchod, gallwch ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol rydych chi'n eu hadnabod.";
"settings_identity_server_no_is" = "Dim gweinyddwr adnabod wedi'i osod";
"settings_identity_server_no_is" = "Dim gweinydd adnabod wedi'i osod";
"settings_identity_server_no_is_description" = "Ar hyn o bryd nid ydych yn defnyddio gweinydd adnabod. I ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol rydych chi'n eu hadnabod, ychwanegwch un uchod.";
// Identity server settings
"identity_server_settings_title" = "Gweinydd Adnabod";
@ -538,3 +538,348 @@
"room_details_fail_to_update_room_canonical_alias" = "Methwyd diweddaru'r prif gyfeiriad";
"room_details_fail_to_update_room_communities" = "Methwyd diweddaru'r cymunedau cysylltiedig";
"room_details_fail_to_update_room_direct" = "Methwyd diweddaru baner uniongyrchol yr ystafell hon";
"room_details_fail_to_enable_encryption" = "Methwyd galluogi amgryptio yn yr ystafell hon";
"room_details_save_changes_prompt" = "Hoffech chi gadw'r newidiadau?";
"room_details_set_main_address" = "Gosod fel Prif Gyfeiriad";
"room_details_unset_main_address" = "Dad-osod fel Prif Gyfeiriad";
"room_details_copy_room_id" = "Copio ID Ystafell";
"room_details_copy_room_address" = "Copio Cyfeiriad Ystafell";
"room_details_copy_room_url" = "Copio URL Ystafell";
// Group Details
"group_details_title" = "Manylion Cymuned";
"group_details_home" = "Hafan";
"group_details_people" = "Pobl";
"group_details_rooms" = "Ystafelloedd";
// Group Home
"group_home_one_member_format" = "1 aelod";
"group_home_multi_members_format" = "%tu aelod";
"group_home_one_room_format" = "1 ystafell";
"group_home_multi_rooms_format" = "%tu ystafell";
"group_invitation_format" = "Mae %@ wedi eich gwahodd i ymuno â'r gymuned hon";
// Group participants
"group_participants_add_participant" = "Ychwanegu cyfranogwr";
"group_participants_leave_prompt_title" = "Gadael grŵp";
"group_participants_leave_prompt_msg" = "Ydych chi'n siŵr eich bod chi eisiau gadael y grŵp?";
"group_participants_remove_prompt_title" = "Cadarnhad";
"group_participants_remove_prompt_msg" = "Ydych chi'n siwr eich bod chi eisiau tynnu %@ o'r grŵp?";
"group_participants_invite_prompt_title" = "Cadarnhad";
"group_participants_invite_prompt_msg" = "Ydych chi'n siwr eich bod eisau gwahodd %@ i'r grŵp?";
"group_participants_filter_members" = "Hidlo aelodau'r cymuned";
"group_participants_invite_another_user" = "Chwilio / gwahodd yn ôl ID Defnyddiwr neu Enw";
"group_participants_invite_malformed_id_title" = "Gwall gwahoddiad";
"group_participants_invite_malformed_id" = "ID camffurfiedig. Dylai fod yn ID Matrix fel '@localpart:domain'";
"group_participants_invited_section" = "GWAHODDWYD";
// Group rooms
"group_rooms_filter_rooms" = "Hidlo ystafelloedd y gymuned";
// Read Receipts
"read_receipts_list" = "Rhestr Derbynebau Darllen";
"receipt_status_read" = "Wedi darllen: ";
// Media picker
"media_picker_title" = "Llyfrgell cyfryngau";
"media_picker_library" = "Llyfrgell";
"media_picker_select" = "Dewis";
// Image picker
"image_picker_action_camera" = "Tynnu llun";
"image_picker_action_library" = "Dewis o'r llyfrgell";
// Directory
"directory_title" = "Cyfeiriadur";
"directory_server_picker_title" = "Dewisiwch gyfeiriadur";
"directory_server_all_rooms" = "Pob ystafell ar weinydd %@";
"directory_server_all_native_rooms" = "Pob ystafell Matrix frodorol";
"directory_server_type_homeserver" = "Teipiwch hafanweinydd i restru ystafelloedd cyhoeddus o";
"directory_server_placeholder" = "matrix.org";
// Events formatter
"event_formatter_member_updates" = "%tu newidiadau aelodaeth";
"event_formatter_widget_added" = "Ychwanegwyd teclyn %@ gan %@";
"event_formatter_widget_removed" = "Tynnwyd teclyn %@ gan %@";
"event_formatter_jitsi_widget_added" = "Ychwanegwyd cynhadledd VoIP gan %@";
"event_formatter_jitsi_widget_removed" = "Tynnwyd cynhadledd VoIP gan %@";
"event_formatter_rerequest_keys_part1_link" = "Ail-ofyn am allweddi amgryptio";
"event_formatter_rerequest_keys_part2" = " o'ch dyfeisiau eraill.";
"event_formatter_message_edited_mention" = "(adolygwyd)";
// Others
"or" = "neu";
"you" = "Chi";
"today" = "Heddiw";
"yesterday" = "Ddoe";
"network_offline_prompt" = "Mae'n ymddangos bod y cysylltiad Rhyngrwyd yn all-lein.";
"homeserver_connection_lost" = "Methu cysylltu â'r hafanweinydd.";
"public_room_section_title" = "Ystafelloedd Cyhoeddus (yn %@):";
"bug_report_prompt" = "Daeth yr app ar draws nam pall y tro diwethaf iddo redeg. Hoffech chi gyflwyno adroddiad pall?";
"rage_shake_prompt" = "Mae'n ymddangos eich bod yn ysgwyd y ffôn mewn rhwystredigaeth. Hoffech chi gyflwyno adroddiad nam?";
"do_not_ask_again" = "Peidio â gofyn eto";
"camera_access_not_granted" = "Nid oes gan %@ ganiatâd i ddefnyddio'r Camera, newidiwch y gosodiadau preifatrwydd";
"camera_unavailable" = "Nid yw'r camera ar gael ar eich dyfais";
"photo_library_access_not_granted" = "Nid oes gan %@ ganiatâd i gael mynediad i'r llyfrgell ffotograffau, newidiwch osodiadau preifatrwydd";
"large_badge_value_k_format" = "%.1fK";
"room_does_not_exist" = "Nid yw %@ yn bodoli";
// Call
"call_incoming_voice_prompt" = "Galwad llais sy'n dod i mewn gan %@";
"call_incoming_video_prompt" = "Galwad fideo sy'n dod i mewn gan %@";
"call_incoming_voice" = "Galwad sy'n dod i mewn...";
"call_incoming_video" = "Galwad fideo sy'n dod i mewn...";
"call_already_displayed" = "Mae galwad ar y gweill eisoes.";
"call_jitsi_error" = "Methwyd ymuno â'r alwad cynhadledd.";
"call_no_stun_server_error_title" = "Methodd yr alwad oherwydd gweinydd wedi'i gamosod";
"call_no_stun_server_error_message_1" = "Gofynnwch i weinyddwr eich hafanweinydd %@ i osod gweinydd TURN er mwyn i alwadau weithio'n ddibynadwy.";
"call_no_stun_server_error_message_2" = "Fel arall, gallwch geisio defnyddio'r gweinydd cyhoeddus yn %@, ond ni fydd hyn mor ddibynadwy, a bydd yn rhannu eich cyfeiriad IP gyda'r gweinydd hwnnw. Gallwch hefyd reoli hyn yn Gosodiadau";
"call_no_stun_server_error_use_fallback_button" = "Rhowch gynnig ar ddefnyddio %@";
// No VoIP support
"no_voip_title" = "Galwad sy'n dod i mewn";
"no_voip" = "Mae %@ yn eich ffonio ond nid yw %@ yn cefnogi galwadau eto.\nGallwch anwybyddu'r hysbysiad hwn ac ateb yr alwad o ddyfais arall neu gallwch ei wrthod.";
// Crash report
"google_analytics_use_prompt" = "Hoffech chi helpu i wella %@ trwy gyrru adroddiadau pall a data defnydd dienw yn awtomatig?";
// Crypto
"e2e_enabling_on_app_update" = "Mae Riot bellach yn cefnogi amgryptio o'r dechrau i'r diwedd ond mae angen i chi fewngofnodi eto i'w alluogi.\n\nGallwch ei wneud nawr neu'n hwyrach o'r gosodiadau.";
"e2e_need_log_in_again" = "Mae angen i chi fewngofnodi i gynhyrchu allweddi amgryptio o'r dechrau i'r diwedd ar gyfer y ddyfais hon a chyflwyno'r allwedd gyhoeddus i'ch hafanweinydd\nDim ond unwaith fydd rhaid gwneud hyn; sori am yr anghyfleustra.";
// Key backup wrong version
"e2e_key_backup_wrong_version_title" = "Copi Allwedd Wrth Gefn Newydd";
"e2e_key_backup_wrong_version" = "Mae copi allwedd wrth gefn newydd neges ddiogel wedi'i ganfod.\n\nOs nad chi oedd hyn, gosodwch gyfrinair newydd yn Gosodiadau.";
"e2e_key_backup_wrong_version_button_settings" = "Gosodiadau";
"e2e_key_backup_wrong_version_button_wasme" = "Fi oedd e";
// Bug report
"bug_report_title" = "Adroddiad Nam";
"bug_report_description" = "Disgrifiwch y nam. Beth wnaethoch chi? Beth oeddech chi'n disgwyl iddo ddigwydd? Beth ddigwyddodd mewn gwirionedd?";
"bug_crash_report_title" = "Adroddiad Pall";
"bug_crash_report_description" = "Disgrifiwch yr hyn a wnaethoch cyn y pall:";
"bug_report_logs_description" = "Er mwyn canfod problemau, anfonir logiau gan y cleient hwn gyda'r adroddiad nam hwn. Os byddai'n well gennych anfon y testun uchod yn unig, dad-gliciwch:";
"bug_report_send_logs" = "Anfon logiau";
"bug_report_send_screenshot" = "Anfon sgrinlun";
"bug_report_progress_zipping" = "Casglu logiau";
"bug_report_progress_uploading" = "Uwchlwytho adroddiad";
"bug_report_send" = "Anfon";
// Widget
"widget_no_integrations_server_configured" = "Dim gweinydd integreiddiadau wedi'i osod";
"widget_integrations_server_failed_to_connect" = "Methwyd cysylltu â'r gweinydd integreiddiadau";
"widget_no_power_to_manage" = "Mae angen caniatâd arnoch i reoli teclynnau yn yr ystafell hon";
"widget_creation_failure" = "Methwyd creu teclyn";
"widget_sticker_picker_no_stickerpacks_alert" = "Ar hyn o bryd nid oes gennych unrhyw becyn sticeri wedi'u galluogi.";
"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Ychwanegu rhai rwan?";
// Widget Integration Manager
"widget_integration_need_to_be_able_to_invite" = "Mae angen i chi allu gwahodd defnyddwyr i wneud hynny.";
"widget_integration_unable_to_create" = "Methu creu teclyn.";
"widget_integration_failed_to_send_request" = "Methwyd i anfon cais.";
"widget_integration_room_not_recognised" = "Ni gydnabyddir yr ystafell hon.";
"widget_integration_positive_power_level" = "Rhaid i lefel pŵer fod yn gyfanrif positif.";
"widget_integration_must_be_in_room" = "Nid ydych yn yr ystafell hon.";
"widget_integration_no_permission_in_room" = "Nid oes gennych ganiatâd i wneud hynny yn yr ystafell hon.";
"widget_integration_missing_room_id" = "room_id ar goll yn y cais.";
"widget_integration_missing_user_id" = "user_id ar goll yn y cais.";
"widget_integration_room_not_visible" = "Nid yw ystafell %@ yn weladwy.";
// Widget Picker
"widget_picker_title" = "Integreiddiadau";
// Share extension
"share_extension_auth_prompt" = "Mewngofnodwch yn y prif app i rannu cynnwys";
"share_extension_failed_to_encrypt" = "Methwyd anfon. Gwiriwch y gosodiadau amgryptio ar gyfer yr ystafell hon yn y prif app";
// Room key request dialog
"e2e_room_key_request_title" = "Cais allwedd amgryptio";
"e2e_room_key_request_message_new_device" = "Fe wnaethoch ychwanegu dyfais newydd '%@', sy'n gofyn am allweddi amgryptio.";
"e2e_room_key_request_message" = "Mae'ch dyfais anwiriedig '%@' yn gofyn am allweddi amgryptio.";
"e2e_room_key_request_start_verification" = "Dechrau gwirio...";
"e2e_room_key_request_share_without_verifying" = "Rhannwch heb wirio";
"e2e_room_key_request_ignore_request" = "Anwybyddu cais";
// GDPR
"gdpr_consent_not_given_alert_message" = "Er mwyn parhau i ddefnyddio'r hafanweinydd %@ rhaid i chi adolygu a chytuno i'r telerau ac amodau.";
"gdpr_consent_not_given_alert_review_now_action" = "Adolygu rwan";
// Service terms
"service_terms_modal_title" = "Telerau Gwasanaeth";
"service_terms_modal_message" = "I barhau maen' rhaid i chi dderbyn telerau y gwasanaeth hwn (%@).";
"service_terms_modal_accept_button" = "Derbyn";
"service_terms_modal_decline_button" = "Gwrthod";
"service_terms_modal_description_for_identity_server_1" = "Dod o hyd i eraill dros y ffôn neu e-bost";
"service_terms_modal_description_for_identity_server_2" = "Byddwch i'w gweld dros ffôn neu e-bost";
"service_terms_modal_description_for_integration_manager" = "Defnyddiwch Botiau, pontydd, teclynnau a phecynnau sticeri";
// Service terms - Variant for identity server when displayed out of a context
"service_terms_modal_title_identity_server" = "Darganfod Cysylltiadau";
"service_terms_modal_message_identity_server" = "Derbyn telerau'r gweinydd adnabod (%@) i ddarganfod cysylltiadau.";
"deactivate_account_title" = "Dad-actifadu Cyfrif";
"deactivate_account_informations_part1" = "Bydd hyn yn golygu na ellir defnyddio'ch cyfrif yn barhaol. Ni fyddwch yn gallu mewngofnodi, ac ni fydd unrhyw un yn gallu ailgofrestru'r un ID defnyddiwr. Bydd hyn yn achosi i'ch cyfrif adael yr holl ystafelloedd y mae'n cymryd rhan ynddynt, a bydd yn tynnu manylion eich cyfrif o'ch gweinydd adnabod. ";
"deactivate_account_informations_part2_emphasize" = "Ni ellir gwrthdroi'r weithred hon.";
"deactivate_account_informations_part3" = "\n\nYn dad-actifadu eich cyfrif ";
"deactivate_account_informations_part4_emphasize" = "nid yw yn ddiofyn yn achosi inni anghofio negeseuon yr ydych wedi'u hanfon. ";
"deactivate_account_informations_part5" = "Os hoffech i ni anghofio'ch negeseuon, ticiwch y blwch isod\n\nMae gwelededd neges yn Matrix yn debyg i e-bost. Mae anghofio'ch negeseuon yn golygu na fydd negeseuon rydych chi wedi'u hanfon yn cael eu rhannu ag unrhyw ddefnyddwyr newydd neu anghofrestredig, ond bydd defnyddwyr cofrestredig sydd eisoes â mynediad at y negeseuon hyn yn dal i gael mynediad i'w copi.";
"deactivate_account_forget_messages_information_part1" = "Anghofiwch yr holl negeseuon yr wyf wedi'u hanfon pan fydd fy nghyfrif yn cael ei ddad-actifadu (";
"deactivate_account_forget_messages_information_part2_emphasize" = "Rhybudd";
"deactivate_account_forget_messages_information_part3" = ": bydd hyn yn achosi i ddefnyddwyr y dyfodol weld golwg anghyflawn o sgyrsiau)";
"deactivate_account_validate_action" = "Dad-actifadu cyfrif";
"deactivate_account_password_alert_title" = "Dad-actifadu Cyfrif";
"deactivate_account_password_alert_message" = "I barhau, rhowch eich cyfrinair os gwelwch yn dda";
// Re-request confirmation dialog
"rerequest_keys_alert_title" = "Anfonwyd y Cais";
"rerequest_keys_alert_message" = "Lansiwch Riot ar ddyfais arall a all ddadgryptio'r neges fel y gall anfon yr allweddi i'r ddyfais hon.";
"key_backup_setup_title" = "Allweddi Wrth Gefn";
"key_backup_setup_skip_alert_title" = "Ydych chi'n siwr?";
"key_backup_setup_skip_alert_message" = "Efallai y byddwch chi'n colli negeseuon diogel os byddwch chi'n allgofnodi neu'n colli'ch dyfais.";
"key_backup_setup_skip_alert_skip_action" = "Sgipio";
"key_backup_setup_intro_title" = "Peidiwch byth â cholli negeseuon wedi'u hamgryptio";
"key_backup_setup_intro_info" = "Diogelir negeseuon mewn ystafelloedd amgryptiedig gydag amgryptio o'r dechrau i'r diwedd. Dim ond chi a'r derbynnydd / derbynwyr sydd â'r allweddi i ddarllen y negeseuon hyn.\n\nGwnewch copi wrth gefn o'ch allweddi yn ddiogel er mwyn osgoi eu colli.";
"key_backup_setup_intro_setup_action_without_existing_backup" = "Dechrau defnyddio Allweddi Wrth Gefn";
"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Cysylltwch y ddyfais hon i Allweddi Wrth Gefn";
"key_backup_setup_intro_manual_export_info" = "(Uwch)";
"key_backup_setup_intro_manual_export_action" = "Allfudo allweddi â llaw";
"key_backup_setup_passphrase_title" = "Diogelwch eich copi wrth gefn gyda chyfrinair";
"key_backup_setup_passphrase_info" = "Byddwn yn storio copi wedi'i amgryptio o'ch allweddi ar ein gweinydd. Amddiffynwch eich copi wrth gefn gyda chyfrinair i'w gadw'n ddiogel.\n\nEr mwyn sicrhau'r diogelwch mwyaf, dylai hyn fod yn wahanol i gyfrinair eich cyfrif.";
"key_backup_setup_passphrase_passphrase_title" = "Cyfrinair";
"key_backup_setup_passphrase_passphrase_placeholder" = "Rhowch cyfrinair";
"key_backup_setup_passphrase_passphrase_valid" = "Gwych!";
"key_backup_setup_passphrase_passphrase_invalid" = "Ceisiwch ychwanegu gair";
"key_backup_setup_passphrase_confirm_passphrase_title" = "Cadarnhau";
"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Cadarnhau cyfrinair";
"key_backup_setup_passphrase_confirm_passphrase_valid" = "Gwych!";
"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Nid yw'r cyfrinair yn cyfateb";
"key_backup_setup_passphrase_set_passphrase_action" = "Cadw Cyfrinair";
"key_backup_setup_passphrase_setup_recovery_key_info" = "Neu, diogelwch eich copi wrth gefn gydag Allwedd Adfer, gan ei arbed yn rhywle diogel.";
"key_backup_setup_passphrase_setup_recovery_key_action" = "(Uwch) Gosod gydag Allwedd Adfer";
"key_backup_setup_success_title" = "Llwyddiant!";
// Success from passphrase
"key_backup_setup_success_from_passphrase_info" = "Mae'ch allweddi yn cael eu cadw wrth gefn.\n\nRhwyd ddiogelwch yw eich allwedd adfer - gallwch ei defnyddio i adfer mynediad i'ch negeseuon amgryptiedig os byddwch chi'n anghofio'ch cyfrinair.\n\nCadwch eich allwedd adfer yn rhywle diogel iawn, fel rheolwr cyfrinair (neu dan glo).";
"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Cadw Allwedd Adfer";
"key_backup_setup_success_from_passphrase_done_action" = "Wedi Gorffen";
// Success from recovery key
"key_backup_setup_success_from_recovery_key_info" = "Mae'ch allweddi yn cael eu cadw wrth gefn.\n\nGwnewch gopi o'r allwedd adfer hon a'i chadw'n ddiogel.";
"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Allwedd Adfer";
"key_backup_setup_success_from_recovery_key_make_copy_action" = "Gwneud Copi";
"key_backup_setup_success_from_recovery_key_made_copy_action" = "Dwi wedi gwneud copi";
"key_backup_recover_title" = "Negeseuon Diogel";
"key_backup_recover_invalid_passphrase_title" = "Cyfrinair Adfer Anghywir";
"key_backup_recover_invalid_passphrase" = "Ni ellid dadgryptio copi wrth gefn gyda'r cyfrinair hwn: gwiriwch eich bod wedi nodi'r cyfrinair adfer cywir.";
"key_backup_recover_invalid_recovery_key_title" = "Camgymhariad Allwedd Adfer";
"key_backup_recover_invalid_recovery_key" = "Ni ellid dadgryptio copi wrth gefn gyda'r allwedd hon: gwiriwch eich bod wedi nodi'r allwedd adfer gywir.";
"key_backup_recover_from_passphrase_info" = "Defnyddiwch eich cyfrinair adfer i ddatgloi eich hanes neges ddiogel";
"key_backup_recover_from_passphrase_passphrase_title" = "Cyfrinair";
"key_backup_recover_from_passphrase_passphrase_placeholder" = "Rhowch cyfrinair";
"key_backup_recover_from_passphrase_recover_action" = "Datgloi Hanes";
"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Ddim yn gwybod eich cyfrinair adfer? Gallwch chi ";
"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "ddefnyddio eich allwedd adfer";
"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = ".";
"key_backup_recover_from_recovery_key_info" = "Defnyddiwch eich allwedd adfer i ddatgloi eich hanes neges ddiogel";
"key_backup_recover_from_recovery_key_recovery_key_title" = "Allwedd Adfer";
"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Rhowch Allwedd Adfer";
"key_backup_recover_from_recovery_key_recover_action" = "Datgloi Hanes";
"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Wedi colli'ch allwedd adfer? Gallwch chi sefydlu un newydd yn gosodiadau.";
"key_backup_recover_success_info" = "Copi wrth gefn wedi'i Adfer!";
"key_backup_recover_done_action" = "Wedi Gorffen";
"key_backup_setup_banner_title" = "Peidiwch byth â cholli negeseuon wedi'u hamgryptio";
"key_backup_setup_banner_subtitle" = "Dechrau defnyddio Allweddi Wrth Gefn";
"key_backup_recover_banner_title" = "Peidiwch byth â cholli negeseuon wedi'u hamgryptio";
"key_backup_recover_connent_banner_subtitle" = "Cysylltwch y ddyfais hon i Allweddi Wrth Gefn";
"sign_out_existing_key_backup_alert_title" = "Ydych chi'n siŵr eich bod chi am allgofnodi?";
"sign_out_existing_key_backup_alert_sign_out_action" = "Allgofnodi";
"sign_out_non_existing_key_backup_alert_title" = "Byddwch yn colli mynediad i'ch negeseuon amgryptiedig os byddwch chi'n allgofnodi rwan";
"sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Dechrau defnyddio Allweddi Wrth Gefn";
"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Dydw i ddim eisiau i fy negeseuon amgryptiedig";
"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Byddwch yn colli ei'ch negeseuon amgryptiedig";
"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Byddwch yn colli mynediad i'ch negeseuon amgryptiedig oni bai eich bod yn gwneud copi wrth gefn o'ch allweddi cyn allgofnodi.";
"sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Allgofnodi";
"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Creu Copi Wrth Gefn";
"sign_out_key_backup_in_progress_alert_title" = "Creu copi allwedd wrth gefn ar y gweill. Os byddwch chi'n allgofnodi nawr byddwch chi'n colli mynediad i'ch negeseuon wedi'u hamgryptio.";
"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Dydw i ddim eisiau i fy negeseuon amgryptiedig";
"sign_out_key_backup_in_progress_alert_cancel_action" = "Arosaf";
// MARK: - Device Verification
"device_verification_title" = "Gwirio dyfais";
"device_verification_security_advice" = "Er mwyn sicrhau'r diogelwch mwyaf, rydym yn argymell eich bod yn gwneud hyn yn bersonol neu'n defnyddio dull cyfathrebu dibynadwy arall";
"device_verification_cancelled" = "Canslodd y parti arall y gwiriad.";
"device_verification_cancelled_by_me" = "Mae'r gwiriad wedi'i ganslo. Rheswm: %@";
"device_verification_error_cannot_load_device" = "Methu llwytho gwybodaeth am ddyfeisiau.";
// Mark: Incoming
"device_verification_incoming_title" = "Cais Gwirio sy'n Dod i Mewn";
"device_verification_incoming_description_1" = "Gwiriwch y ddyfais hon i'w nodi fel un y gellir ymddiried ynddo. Mae dyfeisiau ymddiriedol partneriaid yn rhoi tawelwch meddwl ychwanegol i chi wrth ddefnyddio negeseuon wedi'u hamgryptio o'r dechrau i'r diwedd.";
"device_verification_incoming_description_2" = "Bydd gwirio'r ddyfais hon yn ei nodi fel un y gellir ymddiried ynddo, a hefyd yn marcio'ch dyfais fel un y gellir ymddiried ynddo i'r partner.";
// MARK: Start
"device_verification_start_title" = "Gwirio trwy gymharu testun byr";
"device_verification_start_wait_partner" = "Aros i'r partner dderbyn ...";
"device_verification_start_use_legacy" = "Dim byd yn ymddangos? Nid yw pob cleient yn cefnogi gwirio rhyngweithiol eto. Defnyddiwch yr hen fodd o wirio.";
"device_verification_start_verify_button" = "Dechrau Gwirio";
"device_verification_start_use_legacy_action" = "Defnyddio'r Hen Fodd o Wirio";
// MARK: Verify
"device_verification_verify_title_emoji" = "Gwiriwch y ddyfais hon trwy gadarnhau'r emoji canlynol sy'n ymddangos ar sgrin y partner";
"device_verification_verify_title_number" = "Gwiriwch y ddyfais hon trwy gadarnhau'r rhifau canlynol sy'n ymddangos ar sgrin y partner";
"device_verification_verify_wait_partner" = "Aros i'r partner gadarnhau...";
// MARK: Verified
"device_verification_verified_title" = "Wedi Gwirio!";
"device_verification_verified_description_1" = "Rydych chi wedi gwirio'r ddyfais hon yn llwyddiannus.";
"device_verification_verified_description_2" = "Mae negeseuon diogel gyda'r defnyddiwr hwn wedi'u hamgryptio o'r dechrau i'r diwedd ac ni all unrhyw drydydd parti eu darllen.";
"device_verification_verified_got_it_button" = "Iawn";
// MARK: Emoji
"device_verification_emoji_dog" = "Ci";
"device_verification_emoji_cat" = "Cath";
"device_verification_emoji_lion" = "Llew";
"device_verification_emoji_horse" = "Ceffyl";
"device_verification_emoji_unicorn" = "Ungorn";
"device_verification_emoji_pig" = "Mochyn";
"device_verification_emoji_elephant" = "Eliffant";
"device_verification_emoji_rabbit" = "Cwningen";
"device_verification_emoji_panda" = "Panda";
"device_verification_emoji_rooster" = "Ceiliog";
"device_verification_emoji_penguin" = "Pengwin";
"device_verification_emoji_turtle" = "Crwban";
"device_verification_emoji_fish" = "Pysgodyn";
"device_verification_emoji_octopus" = "Octopws";
"device_verification_emoji_butterfly" = "Pilipala";
"device_verification_emoji_flower" = "Blodyn";
"device_verification_emoji_tree" = "Coeden";
"device_verification_emoji_cactus" = "Cactws";
"device_verification_emoji_mushroom" = "Madarchen";
"device_verification_emoji_globe" = "Glôb";
"device_verification_emoji_moon" = "Lleuad";
"device_verification_emoji_cloud" = "Cwmwl";
"device_verification_emoji_fire" = "Tân";
"device_verification_emoji_banana" = "Banana";
"device_verification_emoji_apple" = "Afal";
"device_verification_emoji_strawberry" = "Mefusen";
"device_verification_emoji_corn" = "Ŷd";
"device_verification_emoji_pizza" = "Pizza";
"device_verification_emoji_cake" = "Cacen";
"device_verification_emoji_heart" = "Calon";
"device_verification_emoji_smiley" = "Gwenoglun";
"device_verification_emoji_robot" = "Robot";
"device_verification_emoji_hat" = "Het";
"device_verification_emoji_glasses" = "Sbectol";
"device_verification_emoji_spanner" = "Sbaner";
"device_verification_emoji_santa" = "Siôn Corn";
"device_verification_emoji_thumbs up" = "Codi bawd";
"device_verification_emoji_umbrella" = "Ymbarél";
"device_verification_emoji_hourglass" = "Awrwydr";
"device_verification_emoji_clock" = "Cloc";
"device_verification_emoji_gift" = "Rhodd";
"device_verification_emoji_light bulb" = "Bwlb Golau";
"device_verification_emoji_book" = "Llyfr";
"device_verification_emoji_pencil" = "Pensil";
"device_verification_emoji_paperclip" = "Clip Papur";
"device_verification_emoji_scissors" = "Siswrn";
"device_verification_emoji_lock" = "Clo";
"device_verification_emoji_key" = "Allwedd";
"device_verification_emoji_hammer" = "Mwrthwl";
"device_verification_emoji_telephone" = "Ffôn";
"device_verification_emoji_flag" = "Baner";
"device_verification_emoji_train" = "Trên";
"device_verification_emoji_bicycle" = "Beic";
"device_verification_emoji_aeroplane" = "Awyren";
"device_verification_emoji_rocket" = "Roced";
"device_verification_emoji_trophy" = "Tlws";
"device_verification_emoji_ball" = "Pêl";
"device_verification_emoji_guitar" = "Gitâr";
"device_verification_emoji_trumpet" = "Trwmped";
"device_verification_emoji_bell" = "Cloch";
"device_verification_emoji_anchor" = "Angor";
"device_verification_emoji_headphones" = "Clustffonau";
"device_verification_emoji_folder" = "Plygell";
"device_verification_emoji_pin" = "Pin";
// MARK: File upload
"file_upload_error_title" = "Uwchlwythiad Ffeil";
"file_upload_error_unsupported_file_type_message" = "Ni gefnogir y yma math o ffeil.";
// MARK: Emoji picker
"emoji_picker_title" = "Ymatebion";
"emoji_picker_people_category" = "Gwenogluniau & Pobl";
"emoji_picker_nature_category" = "Anifeiliaid & Natur";
"emoji_picker_foods_category" = "Bwyd & Diod";
"emoji_picker_activity_category" = "Gweithgareddau";
"emoji_picker_places_category" = "Teithio & Llefydd";
"emoji_picker_objects_category" = "Gwrthrychau";
"emoji_picker_symbols_category" = "Symbolau";
"emoji_picker_flags_category" = "Baneri";
// MARK: Reaction history
"reaction_history_title" = "Ymatebion";
// Generic errors
"error_invite_3pid_with_no_identity_server" = "Ychwanegwch weinydd adnabod yn eich gosodiadau i wahodd trwy e-bost.";
"error_not_supported_on_mobile" = "Ni allwch wneud hyn o %@ ffôn symudol.";

View file

@ -897,3 +897,10 @@
"settings_add_3pid_invalid_password_message" = "Ungültiges Passwort";
"identity_server_settings_disconnect_info" = "Wenn Sie die Verbindung zu Ihrem Identitätsserver trennen, werden Sie von anderen Benutzern nicht erkannt und können andere per E-Mail oder Telefon einladen.";
"error_not_supported_on_mobile" = "Dies ist in %@ mobile nicht möglich.";
"settings_integrations" = "INTEGRATIONEN";
"settings_integrations_allow_button" = "Integrationen verwalten";
"widget_menu_refresh" = "Aktualisierung";
"widget_menu_open_outside" = "Im Browser öffnen";
"widget_menu_revoke_permission" = "Zugriff für mich widerrufen";
"widget_menu_remove" = "Für alle entfernen";
"widget_integration_manager_disabled" = "Sie müssen den Integration Manager in den Einstellungen aktivieren";

View file

@ -58,6 +58,9 @@
"sending" = "Sending";
"close" = "Close";
// Accessibility
"accessibility_checkbox_label" = "checkbox";
// Authentication
"auth_login" = "Log in";
"auth_register" = "Register";
@ -378,6 +381,7 @@
"settings_calls_settings" = "CALLS";
"settings_discovery_settings" = "DISCOVERY";
"settings_identity_server_settings" = "IDENTITY SERVER";
"settings_integrations" = "INTEGRATIONS";
"settings_user_interface" = "USER INTERFACE";
"settings_ignored_users" = "IGNORED USERS";
"settings_contacts" = "LOCAL CONTACTS";
@ -431,6 +435,9 @@
"settings_calls_stun_server_fallback_button" = "Allow fallback call assist server";
"settings_calls_stun_server_fallback_description" = "Allow fallback call assist server %@ when your homeserver does not offer one (your IP address would be shared during a call).";
"settings_integrations_allow_button" = "Manage integrations";
"settings_integrations_allow_description" = "Use an Integration Manager (%@) to manage bots, bridges, widgets and sticker packs.\n\nIntegration Managers receive configuration data, and can modify widgets, send room invites and set power levels on your behalf.";
"settings_ui_language" = "Language";
"settings_ui_theme" = "Theme";
"settings_ui_theme_auto" = "Auto";
@ -451,6 +458,8 @@
"settings_labs_room_members_lazy_loading_error_message" = "Your homeserver does not support lazy loading of room members yet. Try later.";
"settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi";
"settings_labs_message_reaction" = "React to messages with emoji";
"settings_labs_dm_key_verification" = "Key verification by direct message";
"settings_labs_cross_signing" = "Cross-Signing";
"settings_version" = "Version %@";
"settings_olm_version" = "Olm Version %@";
@ -755,6 +764,10 @@
"widget_creation_failure" = "Widget creation has failed";
"widget_sticker_picker_no_stickerpacks_alert" = "You don't currently have any stickerpacks enabled.";
"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Add some now?";
"widget_menu_refresh" = "Refresh";
"widget_menu_open_outside" = "Open in browser";
"widget_menu_revoke_permission" = "Revoke access for me";
"widget_menu_remove" = "Remove for everyone";
// Widget Integration Manager
"widget_integration_need_to_be_able_to_invite" = "You need to be able to invite users to do that.";
@ -767,9 +780,26 @@
"widget_integration_missing_room_id" = "Missing room_id in request.";
"widget_integration_missing_user_id" = "Missing user_id in request.";
"widget_integration_room_not_visible" = "Room %@ is not visible.";
"widget_integration_manager_disabled" = "You need to enable Integration Manager in settings";
// Widget Picker
"widget_picker_title" = "Integrations";
"widget_picker_manage_integrations" = "Manage integrations...";
// Room widget permissions
"room_widget_permission_title" = "Load Widget";
"room_widget_permission_creator_info_title" = "This widget was added by:";
"room_widget_permission_webview_information_title" = "Using it may set cookies and share data with %@:\n";
"room_widget_permission_information_title" = "Using it may share data with %@:\n";
"room_widget_permission_display_name_permission" = "Your display name";
"room_widget_permission_avatar_url_permission" = "Your avatar URL";
"room_widget_permission_user_id_permission" = "Your user ID";
"room_widget_permission_theme_permission" = "Your theme";
"room_widget_permission_widget_id_permission" = "Widget ID";
"room_widget_permission_room_id_permission" = "Room ID";
// Share extension
"share_extension_auth_prompt" = "Login in the main app to share content";
@ -801,6 +831,7 @@
"service_terms_modal_title_identity_server" = "Contact discovery";
"service_terms_modal_message_identity_server" = "Accept the terms of the identity server (%@) to discover contacts.";
"service_terms_modal_policy_checkbox_accessibility_hint" = "Check to accept %@";
// Deactivate account

View file

@ -904,3 +904,23 @@
"settings_add_3pid_password_message" = "Jarraitzeko sartu zure pasahitza";
"settings_add_3pid_invalid_password_message" = "Pasahitz baliogabea";
"error_not_supported_on_mobile" = "Ezin duzu hau %@ mugikorretik egin.";
"settings_integrations" = "INTEGRAZIOAK";
"settings_integrations_allow_button" = "Kudeatu integrazioak";
"settings_integrations_allow_description" = "Erabili integrazio kudeatzaileren bat botak, zubiak, trepetak eta eranskailu multzoak kudeatzeko.\n\nIntegrazio kudeatzaileek konfigurazio datuak jasotzen dituzte, eta trepetak aldatu ditzakete, gelarako gonbidapenak bidali, eta botere mailak zure izenean ezarri.";
"widget_menu_refresh" = "Freskatu";
"widget_menu_open_outside" = "Ireki nabigatzailean";
"widget_menu_revoke_permission" = "Indargabetu sarbidea niretzat";
"widget_menu_remove" = "Kendu denentzat";
"widget_integration_manager_disabled" = "Integrazio kudeatzaileak gaitu behar dituzu ezarpenetan";
// Room widget permissions
"room_widget_permission_title" = "Kargatu trepeta";
"room_widget_permission_creator_info_title" = "Trepeta hau honek gehitu du:";
"room_widget_permission_webview_information_title" = "Hau erabiltzean cookieak ezarri litezke eta %@ zerbitzariarekin datuak partekatu:\n";
"room_widget_permission_information_title" = "Hau erabiltzean %@ zerbitzariarekin datuak partekatu litezke:\n";
"room_widget_permission_display_name_permission" = "Zure pantaila-izena";
"room_widget_permission_avatar_url_permission" = "Zure abatarraren URL-a";
"room_widget_permission_user_id_permission" = "Zure erabiltzaile ID-a";
"room_widget_permission_theme_permission" = "Zure gaia";
"room_widget_permission_widget_id_permission" = "Trepetaren ID-a";
"room_widget_permission_room_id_permission" = "Gelaren ID-a";
"widget_picker_manage_integrations" = "Integrazioak kudeatu...";

View file

@ -916,3 +916,31 @@
"settings_add_3pid_password_message" = "Pour continuer, saisissez votre mot de passe";
"settings_add_3pid_invalid_password_message" = "Mot de passe non valide";
"error_not_supported_on_mobile" = "Vous ne pouvez pas faire cela depuis %@ mobile.";
"widget_menu_refresh" = "Actualiser";
"widget_menu_open_outside" = "Ouvrir dans le navigateur";
"widget_menu_revoke_permission" = "Révoquer laccès pour moi";
"widget_menu_remove" = "Supprimer pour tout le monde";
"settings_integrations" = "INTÉGRATIONS";
"settings_integrations_allow_button" = "Gérer les intégrations";
"settings_integrations_allow_description" = "Utilisez un gestionnaire dintégrations (%@) pour gérer les bots, les passerelles, les widgets et les packs de stickers.\n\nLes gestionnaires dintégration reçoivent des données de configuration et peuvent modifier les widgets, envoyer des invitations de salon et définir des rangs à votre place.";
"widget_integration_manager_disabled" = "Vous devez activer le gestionnaire dintégrations dans les paramètres";
"widget_room_permission_title" = "Charger le widget";
"widget_room_permission_creator_info_title" = "Ce widget a été ajouté par :";
"widget_room_permission_information" = "Son utilisation peut utiliser des cookies et partager des données avec %@ :\n\n• Votre nom affiché\n• LURL de votre avatar\n• Votre identifiant dutilisateur\n• Votre thème\n• Lidentifiant du salon\n• Lidentifiant du widget";
// Room widget permissions
"room_widget_permission_title" = "Charger un widget";
"room_widget_permission_creator_info_title" = "Ce widget a été ajouté par :";
"room_widget_permission_webview_information_title" = "Son utilisation peut entraîner lutilisation de cookies et le partage de données avec %@ :\n";
"room_widget_permission_information_title" = "Son utilisation peut entraîner le partage de données avec %@ :\n";
"room_widget_permission_display_name_permission" = "Votre nom affiché";
"room_widget_permission_avatar_url_permission" = "LURL de votre avatar";
"room_widget_permission_user_id_permission" = "Votre identifiant dutilisateur";
"room_widget_permission_theme_permission" = "Votre thème";
"room_widget_permission_widget_id_permission" = "Lidentifiant du widget";
"room_widget_permission_room_id_permission" = "Lidentifiant du salon";
// Accessibility
"accessibility_checkbox_label" = "case à cocher";
"widget_picker_manage_integrations" = "Gérer les intégrations…";
"service_terms_modal_policy_checkbox_accessibility_hint" = "Cochez pour accepter %@";
"settings_labs_dm_key_verification" = "Vérification de clé par message direct";
"settings_labs_cross_signing" = "Signature croisée";

View file

@ -921,3 +921,28 @@
"settings_add_3pid_password_message" = "A folytatáshoz add meg a jelszavadat";
"settings_add_3pid_invalid_password_message" = "Érvénytelen jelszó";
"error_not_supported_on_mobile" = "%@ mobilról ezt nem teheted meg.";
"widget_menu_refresh" = "Frissítés";
"widget_menu_open_outside" = "Megnyitás böngészőben";
"widget_menu_revoke_permission" = "Hozzáférés megvonása magamtól";
"widget_menu_remove" = "Visszavonás mindenkitől";
"settings_integrations" = "INTEGRÁCIÓK";
"settings_integrations_allow_button" = "Integrációk kezelése";
"settings_integrations_allow_description" = "Botok, hidak, kisalkalmazások és matrica csomagok kezeléséhez használj Integrációs Menedzsert (%@).\n\nIntegrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet beállíthatja helyetted.";
"widget_integration_manager_disabled" = "Az integrációs menedzsert engedélyezned kell a beállításokban";
// Room widget permissions
"room_widget_permission_title" = "Kisalkalmazás betöltése";
"room_widget_permission_creator_info_title" = "Ezt a kisalkalmazást hozzáadta:";
"room_widget_permission_webview_information_title" = "A használatához lehet, hogy sütiket kell használni és adat lesz megosztva ezzel: %@:\n";
"room_widget_permission_information_title" = "A használatához lehet, hogy adat lesz megosztva ezzel: %@:\n";
"room_widget_permission_display_name_permission" = "Megjelenítési neved";
"room_widget_permission_avatar_url_permission" = "Profilképed URL-je";
"room_widget_permission_user_id_permission" = "Felhasználói azonosítód";
"room_widget_permission_theme_permission" = "Témád";
"room_widget_permission_widget_id_permission" = "Kisalkalmazás azon.";
"room_widget_permission_room_id_permission" = "Szoba azonosító";
"widget_picker_manage_integrations" = "Integrációk kezelése…";
// Accessibility
"accessibility_checkbox_label" = "jelölőnégyzet";
"service_terms_modal_policy_checkbox_accessibility_hint" = "Az engedélyezéshez jelöld be: %@";
"settings_labs_dm_key_verification" = "Kulcs ellenőrzés közvetlen üzenetben";
"settings_labs_cross_signing" = "Kereszt-aláírás";

View file

@ -7,7 +7,7 @@
"title_groups" = "Comunità";
"warning" = "Attenzione";
"next" = "Prossimo";
"leave" = "Lascia";
"leave" = "Esci";
"remove" = "Rimuovi";
"invite" = "Invita";
"cancel" = "Annulla";
@ -61,7 +61,7 @@
// String for App Store
"store_short_description" = "Conversazioni sicure e decentralizzate";
// Actions
"view" = "Vedi";
"view" = "Visualizza";
"back" = "Indietro";
"continue" = "Continua";
"create" = "Crea";
@ -391,7 +391,7 @@
"settings_key_backup_delete_confirmation_prompt_title" = "Elimina backup";
"settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Se non hai eseguito un Backup delle Chiavi perderai i tuoi messaggi crittografati.";
// Room Details
"room_details_title" = "Dettagli stanza";
"room_details_title" = "Dettagli canale";
"room_details_people" = "Membri";
"room_details_files" = "File";
"room_details_settings" = "Impostazioni";
@ -891,3 +891,28 @@
"settings_add_3pid_password_message" = "Per continuare, inserisci la tua password";
"settings_add_3pid_invalid_password_message" = "Password non valida";
"error_not_supported_on_mobile" = "Non puoi farlo da %@ mobile.";
"widget_menu_refresh" = "Ricarica";
"widget_menu_open_outside" = "Apri nel browser";
"widget_menu_revoke_permission" = "Revoca l'accesso a me";
"widget_menu_remove" = "Rimuovi per tutti";
"settings_integrations" = "INTEGRAZIONI";
"settings_integrations_allow_button" = "Gestisci le integrazioni";
"settings_integrations_allow_description" = "Usa un gestore di integrazioni (%@) per gestire bot, bridge, widget e pacchetti di adesivi.\n\nI gestori di integrazione ricevono dati di configurazione e possono modificare widget, inviare inviti alla stanza, assegnare permessi a tuo nome.";
"widget_integration_manager_disabled" = "Devi attivare il gestore di integrazioni nelle impostazioni";
// Room widget permissions
"room_widget_permission_title" = "Carica widget";
"room_widget_permission_creator_info_title" = "Questo widget è stato aggiunto da:";
"room_widget_permission_webview_information_title" = "Usarlo potrebbe impostare cookie e condividere dati con %@:\n";
"room_widget_permission_information_title" = "Usarlo potrebbe condividere dati con %@:\n";
"room_widget_permission_display_name_permission" = "Il tuo nome visualizzato";
"room_widget_permission_avatar_url_permission" = "Il tuo URL dell'avatar";
"room_widget_permission_user_id_permission" = "Il tuo ID utente";
"room_widget_permission_theme_permission" = "Il tuo tema";
"room_widget_permission_widget_id_permission" = "ID widget";
"room_widget_permission_room_id_permission" = "ID stanza";
// Accessibility
"accessibility_checkbox_label" = "checkbox";
"widget_picker_manage_integrations" = "Gestisci integrazioni...";
"service_terms_modal_policy_checkbox_accessibility_hint" = "Seleziona per accettare %@";
"settings_labs_dm_key_verification" = "Verifica chiave via messaggio diretto";
"settings_labs_cross_signing" = "Firma incrociata";

View file

@ -265,7 +265,7 @@
"room_event_action_reaction_show_all" = "모두 보이기";
"room_event_action_reaction_show_less" = "적게 보이기";
"room_event_action_reaction_history" = "리액션 기록";
"room_warning_about_encryption" = "종단간 암호화는 베타 버전이고 신뢰하지 못할 수 있습니다.\n\n아직 데이터를 보호한다고 신뢰하지 마세요.\n\n기기가 방에 참가하기 전에 아직 기록을 해독할 수 없습니다.\n\n아직 암호화를 구현하지 않았기 때문에 암호화된 메시지는 클라이언트에 나타나지 않습니다.";
"room_warning_about_encryption" = "종단간 암호화는 베타 버전이고 신뢰하지 못할 수 있습니다.\n\n아직 데이터를 보호한다고 신뢰하지 마세요.\n\n기기가 방에 참가하기 전에 아직 기록을 복호화할 수 없습니다.\n\n아직 암호화를 구현하지 않았기 때문에 암호화된 메시지는 클라이언트에 나타나지 않습니다.";
"room_event_failed_to_send" = "보내기에 실패함";
"room_action_camera" = "사진 또는 영상 찍기";
"room_action_send_photo_or_video" = "사진 또는 영상 보내기";
@ -629,7 +629,7 @@
"deactivate_account_password_alert_message" = "계속하려면, 비밀번호를 입력해주세요";
// Re-request confirmation dialog
"rerequest_keys_alert_title" = "요청을 보냈습니다";
"rerequest_keys_alert_message" = "메시지를 해독해서 이 기기로 키를 보낼 수 있도록 Riot을 다른 기기에 설치해주세요.";
"rerequest_keys_alert_message" = "메시지를 복호화해서 이 기기로 키를 보낼 수 있도록 Riot을 다른 기기에 설치해주세요.";
"key_backup_setup_title" = "키 백업";
"key_backup_setup_skip_alert_title" = "확신합니까?";
"key_backup_setup_skip_alert_message" = "로그아웃하거나 기기를 잃어버리면 보안 메시지를 잃게 됩니다.";
@ -665,9 +665,9 @@
"key_backup_setup_success_from_recovery_key_made_copy_action" = "사본을 만들었습니다";
"key_backup_recover_title" = "보안 메시지";
"key_backup_recover_invalid_passphrase_title" = "맞지 않는 복구 암호";
"key_backup_recover_invalid_passphrase" = "이 암호로 백업을 해독할 수 없습니다: 올바른 복구 암호를 입력해서 확인해주세요.";
"key_backup_recover_invalid_passphrase" = "이 암호로 백업을 복호화할 수 없습니다: 올바른 복구 암호를 입력해서 확인해주세요.";
"key_backup_recover_invalid_recovery_key_title" = "복구 키가 맞지 않음";
"key_backup_recover_invalid_recovery_key" = "이 키로 백업을 해독할 수 없습니다: 올바른 복구 키를 입력해서 확인해주세요.";
"key_backup_recover_invalid_recovery_key" = "이 키로 백업을 복호화할 수 없습니다: 올바른 복구 키를 입력해서 확인해주세요.";
"key_backup_recover_from_passphrase_info" = "복구 암호를 사용해 보안 메시지 기록을 푸세요";
"key_backup_recover_from_passphrase_passphrase_title" = "입력";
"key_backup_recover_from_passphrase_passphrase_placeholder" = "암호 입력";
@ -889,3 +889,11 @@
"settings_add_3pid_password_message" = "계속하려면 비밀번호를 입력해주세요";
"settings_add_3pid_invalid_password_message" = "잘못된 비밀번호";
"error_not_supported_on_mobile" = "%@ 모바일에서 할 수 없습니다.";
"settings_integrations" = "통합";
"settings_integrations_allow_button" = "통합 관리";
"settings_integrations_allow_description" = "통합 관리자 (%@)를 사용해 봇, 브릿지, 위젯과 스티커 팩을 관리하세요.\n\n통합 관리자는 설정 데이터를 받고 위젯을 수정하거나, 방 초대를 보내고 권한 등급을 설정할 수 있습니다.";
"widget_menu_refresh" = "새로고침";
"widget_menu_open_outside" = "브라우저에서 열기";
"widget_menu_revoke_permission" = "액세스 취소";
"widget_menu_remove" = "모두를 위해 제거";
"widget_integration_manager_disabled" = "설정에서 통합 관리자를 켜야 합니다";

View file

@ -850,3 +850,86 @@
"contacts_address_book_no_identity_server" = "Ska të formësuar shërbyes identitetesh";
"settings_discovery_settings" = "ZBULIM";
"settings_identity_server_settings" = "SHËRBYES IDENTITETESH";
"settings_three_pids_management_information_part1" = "Administroni cilat adresa email apo numra telefonash mund të përdorni për të bërë hyrjen ose për të rimarrë llogarinë tuaj këtu. Kontrolloni cilët mund tju gjejnë ";
"settings_three_pids_management_information_part2" = "Zbulim";
"settings_three_pids_management_information_part3" = ".";
"settings_add_3pid_password_title_email" = "Shtoni adresë email";
"settings_add_3pid_password_title_msidsn" = "Shtoni numër telefoni";
"settings_add_3pid_password_message" = "Që të vazhdohet, ju lutemi, jepni fjalëkalimin tuaj";
"settings_add_3pid_invalid_password_message" = "Fjalëkalim i pavlefshëm";
"settings_devices_description" = "Emri publik i një pajisjeje është i dukshëm për persona me të cilët komunikoni";
"settings_discovery_no_identity_server" = "Spo përdorni ndonjë shërbyes identitetesh. Që të jeni i zbulueshëm nga kontakte ekzistuese që njihni, shtoni një të tillë.";
"settings_discovery_terms_not_signed" = "Pajtohuni me Kushtet e Shërbimit të Shërbyesit të Identiteteve që ti lejoni vetes të jeni i zbulueshëm përmes adrese email ose numri telefoni.";
"settings_discovery_three_pids_management_information_part1" = "Administroni cilat adresa email ose numra telefonash mund të përdorin përdoruesit e tjerë për tju zbuluar dhe ftuar në dhoma. Shtoni ose hiqni prej kësaj liste adresa email ose numra telefonash ";
"settings_discovery_three_pids_management_information_part2" = "Rregullime Përdoruesi";
"settings_discovery_three_pids_management_information_part3" = ".";
"settings_discovery_error_message" = "Ndodhi një gabim. Ju lutemi, riprovoni.";
"settings_discovery_three_pid_details_title_email" = "Administroni email";
"settings_discovery_three_pid_details_information_email" = "Administroni parapëlqime për këtë adresë email, të cilët përdorues të tjerë mund ta përdorin për tju zbuluar dhe ftuar në dhoma. Shtoni ose hiqni adresa email te Llogaritë.";
"settings_discovery_three_pid_details_title_phone_number" = "Administroni numër telefoni";
"settings_discovery_three_pid_details_information_phone_number" = "Administroni parapëlqime për këtë numër telefoni, të cilin mund ta përdorin përdorues të tjerë për tju zbuluar dhe ftuar në dhoma. Shtoni ose hiqni numra telefonash te Llogaritë.";
"settings_discovery_three_pid_details_share_action" = "Ndajeni me të tjerë";
"settings_discovery_three_pid_details_revoke_action" = "Shfuqizoje";
"settings_discovery_three_pid_details_cancel_email_validation_action" = "Anuloni vlerësim email-i";
"settings_discovery_three_pid_details_enter_sms_code_action" = "Jepni kod SMS aktivizimi";
"settings_identity_server_description" = "Duke përdorur shërbyesin e identiteteve më sipër mund të zbuloni dhe të jeni i zbulueshëm nga kontakte ekzistuese që njihni.";
"settings_identity_server_no_is" = "Ska të formësuar shërbyes identitetesh";
"settings_identity_server_no_is_description" = "Spo përdorni ndonjë shërbyes identitetesh. Që të zbuloni dhe të jeni i zbulueshëm nga kontakte ekzistuese që njihni, shtoni një më sipër.";
// Identity server settings
"identity_server_settings_title" = "Shërbyes Identitetesh";
"identity_server_settings_description" = "Po përdorni %@ që të zbuloni dhe të jeni i zbulueshëm nga kontakte ekzistuese që dini.";
"identity_server_settings_no_is_description" = "Spo përdorni ndonjë shërbyes identitetesh. Që të zbuloni dhe të jeni i zbulueshëm nga kontakte ekzistuese, shtoni një më sipër.";
"identity_server_settings_place_holder" = "Jepni një shërbyes identitetesh";
"identity_server_settings_add" = "Shtoje";
"identity_server_settings_change" = "Ndryshojeni";
"identity_server_settings_disconnect_info" = "Shkëputja nga shërbyesi juaj i identiteteve do të thotë se sdo të jeni të zbulueshëm nga përdorues të tjerë dhe as të jeni në gjendje të ftoni të tjerë përmes email-i ose telefoni.";
"identity_server_settings_disconnect" = "Shkëputu";
"identity_server_settings_alert_no_terms_title" = "Shërbyesi i identiteteve ska kushte shërbimi";
"identity_server_settings_alert_no_terms" = "Shërbyesi i identiteteve që keni zgjedhur nuk ka ndonjë kusht shërbimesh. Vazhdoni vetëm nëse i zini besë të zotit të shërbyesit.";
"identity_server_settings_alert_change_title" = "Ndryshoni shërbyes identitetesh";
"identity_server_settings_alert_change" = "Të bëhet shkëputja nga shërbyesi i identiteteve %1$@ dhe të lidhet me %2$@?";
"identity_server_settings_alert_disconnect_title" = "Shkëpute shërbyesin e identiteteve";
"identity_server_settings_alert_disconnect" = "Të bëhet shkëputja nga shërbyesi i identiteteve %@?";
"identity_server_settings_alert_disconnect_button" = "Shkëpute";
"identity_server_settings_alert_disconnect_still_sharing_3pid" = "Ende ndani me të tjerët të dhëna tuajat personale në shërbyesin e identiteteve %@.\n\nKëshillojmë që të hiqni prej shërbyesit të identiteteve adresat tuaj email dhe numrat tuaj të telefonave përpara se të bëni shkëputjen.";
"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Shkëputu, sido qoftë";
"identity_server_settings_alert_error_terms_not_accepted" = "Duhet të pranoni termat e %@ që ta caktoni si shërbyes identitetesh.";
"identity_server_settings_alert_error_invalid_identity_server" = "%@ sështë shërbyes i vlershëm identitetesh.";
"call_no_stun_server_error_title" = "Thirrja dështoi për shkak shërbyesi të keqformësuar";
"call_no_stun_server_error_message_1" = "Që thirrjet të funksionojnë pa probleme, ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj Home %@ të formësojë një shërbyes TURN.";
"call_no_stun_server_error_message_2" = "Ndryshe, mund të provoni të përdorni shërbyesin publik te %@, por kjo sdo të jetë edhe aq e qëndrueshme, dhe adresa juaj IP do ti bëhet e njohur atij shërbyesi. Këtë mund ta bëni edhe që nga Rregullimet";
"call_no_stun_server_error_use_fallback_button" = "Provoni të përdorni %@";
// Widget Picker
"widget_picker_title" = "Integrime";
"service_terms_modal_decline_button" = "Hidhe poshtë";
"service_terms_modal_description_for_identity_server_1" = "Gjeni të tjerë përmes telefoni ose email-i";
"service_terms_modal_description_for_identity_server_2" = "Bëhuni i gjetshëm përmes telefoni ose email-i";
// Service terms - Variant for identity server when displayed out of a context
"service_terms_modal_title_identity_server" = "Zbulim kontaktesh";
"service_terms_modal_message_identity_server" = "Që të zbuloni kontakte, pranoni kushtet e shërbyesit të identiteteve (%@).";
// Generic errors
"error_invite_3pid_with_no_identity_server" = "Që të ftoni me email, shtoni një shërbyes identitetesh, që nga rregullimet tuaja.";
"error_not_supported_on_mobile" = "Këtë smund ta bëni nga %@ për celular.";
// Accessibility
"accessibility_checkbox_label" = "kutizë";
"settings_integrations" = "INTEGRIME";
"settings_integrations_allow_button" = "Administroni integrime";
"settings_integrations_allow_description" = "Përdorni një Përgjegjës Integrimesh (%@) që të administroni robotë, ura, widget-e dhe paketa ngjitësish.\n\nPërgjegjësit e Integrimeve marrin të dhëna formësimi dhe mund të ndryshojnë widget-e, të dërgojnë ftesa për në dhoma dhe të caktojnë shkallë pushteti në emrin tuaj.";
"widget_menu_refresh" = "Rifreskoje";
"widget_menu_open_outside" = "Hape në shfletues";
"widget_menu_revoke_permission" = "Shfuqizo hyrje për mua";
"widget_menu_remove" = "Hiqe për këdo";
"widget_integration_manager_disabled" = "Lypset të aktivizoni Përgjegjës Integrimesh te rregullimet";
"widget_picker_manage_integrations" = "Administroni integrime…";
// Room widget permissions
"room_widget_permission_title" = "Ngarko Widget";
"room_widget_permission_creator_info_title" = "Ky <em>widget</em> qe shtuar nga:";
"room_widget_permission_webview_information_title" = "Përdorimi i tij mund të sjellë depozitim <em>cookies</em> dhe ndarje të dhënash me %@:\n";
"room_widget_permission_information_title" = "Përdorimi i tij mund të sjellë ndarje të dhënash me %@:\n";
"room_widget_permission_display_name_permission" = "Emri juaj në ekran";
"room_widget_permission_avatar_url_permission" = "URL-ja e avatarit tuaj";
"room_widget_permission_user_id_permission" = "ID-ja juaj e përdoruesit";
"room_widget_permission_theme_permission" = "Tema juaj";
"room_widget_permission_widget_id_permission" = "ID Widget-i";
"room_widget_permission_room_id_permission" = "ID Dhome";
"service_terms_modal_policy_checkbox_accessibility_hint" = "I vini shenjë që të pranohet %@";

View file

@ -0,0 +1,29 @@
/*
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
extension FloatingPoint {
/// Returns clamped `self` value.
/// https://gist.github.com/laevandus/6fd35992157fcc9b5660bcbc82ebfb52#file-clampfloatingpoint-swift
///
/// - Parameter range: The closed range in which `self` should be clamped (`0.2...3.3` for example).
/// - Returns: A FloatingPoint clamped value.
func clamped(to range: ClosedRange<Self>) -> Self {
return max(min(self, range.upperBound), range.lowerBound)
}
}

View file

@ -29,4 +29,15 @@ extension UIButton {
titleLabel.numberOfLines = 0
titleLabel.textAlignment = textAlignment
}
/// Set background color as an image.
/// Useful to automatically adjust highlighted background if `adjustsImageWhenHighlighted` property is set to true or disabled background when `adjustsImageWhenDisabled`is set to true.
///
/// - Parameters:
/// - color: The background color to set as an image.
/// - state: The control state for wich to apply this color.
func vc_setBackgroundColor(_ color: UIColor, for state: UIControl.State) {
let image = UIImage.vc_image(from: color)
self.setBackgroundImage(image, for: state)
}
}

View file

@ -32,6 +32,7 @@ internal enum Asset {
internal static let adminIcon = ImageAsset(name: "admin_icon")
internal static let backIcon = ImageAsset(name: "back_icon")
internal static let chevron = ImageAsset(name: "chevron")
internal static let closeButton = ImageAsset(name: "close_button")
internal static let disclosureIcon = ImageAsset(name: "disclosure_icon")
internal static let group = ImageAsset(name: "group")
internal static let logo = ImageAsset(name: "logo")

View file

@ -117,6 +117,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.TemplateScreenViewController>(storyboard: TemplateScreenViewController.self)
}
internal enum WidgetPermissionViewController: StoryboardType {
internal static let storyboardName = "WidgetPermissionViewController"
internal static let initialScene = InitialSceneType<Riot.WidgetPermissionViewController>(storyboard: WidgetPermissionViewController.self)
}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name

View file

@ -14,6 +14,10 @@ internal enum VectorL10n {
internal static var accept: String {
return VectorL10n.tr("Vector", "accept")
}
/// checkbox
internal static var accessibilityCheckboxLabel: String {
return VectorL10n.tr("Vector", "accessibility_checkbox_label")
}
/// Logout all accounts
internal static var accountLogoutAll: String {
return VectorL10n.tr("Vector", "account_logout_all")
@ -2474,6 +2478,46 @@ internal enum VectorL10n {
internal static var roomWarningAboutEncryption: String {
return VectorL10n.tr("Vector", "room_warning_about_encryption")
}
/// Your avatar URL
internal static var roomWidgetPermissionAvatarUrlPermission: String {
return VectorL10n.tr("Vector", "room_widget_permission_avatar_url_permission")
}
/// This widget was added by:
internal static var roomWidgetPermissionCreatorInfoTitle: String {
return VectorL10n.tr("Vector", "room_widget_permission_creator_info_title")
}
/// Your display name
internal static var roomWidgetPermissionDisplayNamePermission: String {
return VectorL10n.tr("Vector", "room_widget_permission_display_name_permission")
}
/// Using it may share data with %@:\n
internal static func roomWidgetPermissionInformationTitle(_ p1: String) -> String {
return VectorL10n.tr("Vector", "room_widget_permission_information_title", p1)
}
/// Room ID
internal static var roomWidgetPermissionRoomIdPermission: String {
return VectorL10n.tr("Vector", "room_widget_permission_room_id_permission")
}
/// Your theme
internal static var roomWidgetPermissionThemePermission: String {
return VectorL10n.tr("Vector", "room_widget_permission_theme_permission")
}
/// Load Widget
internal static var roomWidgetPermissionTitle: String {
return VectorL10n.tr("Vector", "room_widget_permission_title")
}
/// Your user ID
internal static var roomWidgetPermissionUserIdPermission: String {
return VectorL10n.tr("Vector", "room_widget_permission_user_id_permission")
}
/// Using it may set cookies and share data with %@:\n
internal static func roomWidgetPermissionWebviewInformationTitle(_ p1: String) -> String {
return VectorL10n.tr("Vector", "room_widget_permission_webview_information_title", p1)
}
/// Widget ID
internal static var roomWidgetPermissionWidgetIdPermission: String {
return VectorL10n.tr("Vector", "room_widget_permission_widget_id_permission")
}
/// Save
internal static var save: String {
return VectorL10n.tr("Vector", "save")
@ -2546,6 +2590,10 @@ internal enum VectorL10n {
internal static func serviceTermsModalMessageIdentityServer(_ p1: String) -> String {
return VectorL10n.tr("Vector", "service_terms_modal_message_identity_server", p1)
}
/// Check to accept %@
internal static func serviceTermsModalPolicyCheckboxAccessibilityHint(_ p1: String) -> String {
return VectorL10n.tr("Vector", "service_terms_modal_policy_checkbox_accessibility_hint", p1)
}
/// Terms Of Service
internal static var serviceTermsModalTitle: String {
return VectorL10n.tr("Vector", "service_terms_modal_title")
@ -2810,6 +2858,18 @@ internal enum VectorL10n {
internal static var settingsIgnoredUsers: String {
return VectorL10n.tr("Vector", "settings_ignored_users")
}
/// INTEGRATIONS
internal static var settingsIntegrations: String {
return VectorL10n.tr("Vector", "settings_integrations")
}
/// Manage integrations
internal static var settingsIntegrationsAllowButton: String {
return VectorL10n.tr("Vector", "settings_integrations_allow_button")
}
/// Use an Integration Manager (%@) to manage bots, bridges, widgets and sticker packs.\n\nIntegration Managers receive configuration data, and can modify widgets, send room invites and set power levels on your behalf.
internal static func settingsIntegrationsAllowDescription(_ p1: String) -> String {
return VectorL10n.tr("Vector", "settings_integrations_allow_description", p1)
}
/// KEY BACKUP
internal static var settingsKeyBackup: String {
return VectorL10n.tr("Vector", "settings_key_backup")
@ -2910,6 +2970,14 @@ internal enum VectorL10n {
internal static var settingsLabsCreateConferenceWithJitsi: String {
return VectorL10n.tr("Vector", "settings_labs_create_conference_with_jitsi")
}
/// Cross-Signing
internal static var settingsLabsCrossSigning: String {
return VectorL10n.tr("Vector", "settings_labs_cross_signing")
}
/// Key verification by direct message
internal static var settingsLabsDmKeyVerification: String {
return VectorL10n.tr("Vector", "settings_labs_dm_key_verification")
}
/// End-to-End Encryption
internal static var settingsLabsE2eEncryption: String {
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption")
@ -3250,6 +3318,10 @@ internal enum VectorL10n {
internal static var widgetIntegrationFailedToSendRequest: String {
return VectorL10n.tr("Vector", "widget_integration_failed_to_send_request")
}
/// You need to enable Integration Manager in settings
internal static var widgetIntegrationManagerDisabled: String {
return VectorL10n.tr("Vector", "widget_integration_manager_disabled")
}
/// Missing room_id in request.
internal static var widgetIntegrationMissingRoomId: String {
return VectorL10n.tr("Vector", "widget_integration_missing_room_id")
@ -3290,6 +3362,22 @@ internal enum VectorL10n {
internal static var widgetIntegrationsServerFailedToConnect: String {
return VectorL10n.tr("Vector", "widget_integrations_server_failed_to_connect")
}
/// Open in browser
internal static var widgetMenuOpenOutside: String {
return VectorL10n.tr("Vector", "widget_menu_open_outside")
}
/// Refresh
internal static var widgetMenuRefresh: String {
return VectorL10n.tr("Vector", "widget_menu_refresh")
}
/// Remove for everyone
internal static var widgetMenuRemove: String {
return VectorL10n.tr("Vector", "widget_menu_remove")
}
/// Revoke access for me
internal static var widgetMenuRevokePermission: String {
return VectorL10n.tr("Vector", "widget_menu_revoke_permission")
}
/// No integrations server configured
internal static var widgetNoIntegrationsServerConfigured: String {
return VectorL10n.tr("Vector", "widget_no_integrations_server_configured")
@ -3298,6 +3386,10 @@ internal enum VectorL10n {
internal static var widgetNoPowerToManage: String {
return VectorL10n.tr("Vector", "widget_no_power_to_manage")
}
/// Manage integrations...
internal static var widgetPickerManageIntegrations: String {
return VectorL10n.tr("Vector", "widget_picker_manage_integrations")
}
/// Integrations
internal static var widgetPickerTitle: String {
return VectorL10n.tr("Vector", "widget_picker_title")

View file

@ -0,0 +1,65 @@
/*
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 enum RoomMessageURLType: Int {
case appleDataDetector
case http
case dummy
case unknown
}
/// URL parser for room messages.
@objcMembers
final class RoomMessageURLParser: NSObject {
// MARK: - Constants
private enum Scheme {
static let appleDataDetector = "x-apple-data-detectors"
static let http = "http"
static let https = "https"
}
private enum Constants {
static let dummyURL = "#"
}
// MARK: - Public
func parseURL(_ url: URL) -> RoomMessageURLType {
let roomMessageLink: RoomMessageURLType
if let scheme = url.scheme?.lowercased() {
switch scheme {
case Scheme.appleDataDetector:
roomMessageLink = .appleDataDetector
case Scheme.http, Scheme.https:
roomMessageLink = .http
default:
roomMessageLink = .unknown
}
} else if url.absoluteString == Constants.dummyURL {
roomMessageLink = .dummy
} else {
roomMessageLink = .unknown
}
return roomMessageLink
}
}

View file

@ -28,6 +28,18 @@ final class SerializationService: SerializationServiceType {
func deserialize<T: Decodable>(_ data: Data) throws -> T {
return try decoder.decode(T.self, from: data)
}
func deserialize<T: Decodable>(_ object: Any) throws -> T {
let jsonData: Data
if let data = object as? Data {
jsonData = data
} else {
jsonData = try JSONSerialization.data(withJSONObject: object, options: [])
}
return try decoder.decode(T.self, from: jsonData)
}
func serialize<T: Encodable>(_ object: T) throws -> Data {
return try encoder.encode(object)

View file

@ -18,5 +18,7 @@ import Foundation
protocol SerializationServiceType {
func deserialize<T: Decodable>(_ data: Data) throws -> T
func deserialize<T: Decodable>(_ object: Any) throws -> T
func serialize<T: Encodable>(_ object: T) throws -> Data
}

View file

@ -32,6 +32,8 @@ final class RiotSettings: NSObject {
static let pinRoomsWithUnreadMessages = "pinRoomsWithUnread"
static let allowStunServerFallback = "allowStunServerFallback"
static let stunServerFallback = "stunServerFallback"
static let enableCrossSigning = "enableCrossSigning"
static let enableDMKeyVerification = "enableDMKeyVerification"
}
/// Riot Standard Room Member Power Level
@ -121,7 +123,22 @@ final class RiotSettings: NSObject {
UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi)
}
}
var enableDMKeyVerification: Bool {
get {
return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableDMKeyVerification)
} set {
UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableDMKeyVerification)
}
}
var enableCrossSigning: Bool {
get {
return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableCrossSigning)
} set {
UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableCrossSigning)
}
}
// MARK: Calls

View file

@ -0,0 +1,33 @@
/*
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
/// Model for "im.vector.setting.allowed_widgets"
/// https://github.com/vector-im/riot-meta/blob/master/spec/settings.md#tracking-which-widgets-the-user-has-allowed-to-load
struct RiotSettingAllowedWidgets {
let widgets: [String: Bool]
// Widget type -> Server domain -> Bool
let nativeWidgets: [String: [String: Bool]]
}
extension RiotSettingAllowedWidgets: Decodable {
enum CodingKeys: String, CodingKey {
case widgets
case nativeWidgets = "native_widgets"
}
}

View file

@ -0,0 +1,29 @@
/*
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
/// Model for "im.vector.setting.integration_provisioning"
/// https://github.com/vector-im/riot-meta/blob/master/spec/settings.md#selecting-no-provisioning-for-integration-managers
struct RiotSettingIntegrationProvisioning {
let enabled: Bool
}
extension RiotSettingIntegrationProvisioning: Decodable {
enum CodingKeys: String, CodingKey {
case enabled
}
}

View file

@ -0,0 +1,219 @@
/*
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 MatrixSDK
@objc enum WidgetPermission: Int {
case undefined
case granted
case declined
}
/// Shared user settings across all Riot clients.
/// It implements https://github.com/vector-im/riot-meta/blob/master/spec/settings.md
@objcMembers
class RiotSharedSettings: NSObject {
// MARK: - Constants
private enum Settings {
static let breadcrumbs = "im.vector.setting.breadcrumbs"
static let integrationProvisioning = "im.vector.setting.integration_provisioning"
static let allowedWidgets = "im.vector.setting.allowed_widgets"
}
// MARK: - Properties
// MARK: Private
private let session: MXSession
private lazy var serializationService: SerializationServiceType = SerializationService()
// MARK: - Setup
init(session: MXSession) {
self.session = session
}
// MARK: - Public
// MARK: Integration provisioning
var hasIntegrationProvisioningEnabled: Bool {
return getIntegrationProvisioning()?.enabled ?? true
}
func getIntegrationProvisioning() -> RiotSettingIntegrationProvisioning? {
guard let integrationProvisioningDict = getAccountData(forEventType: Settings.integrationProvisioning) else {
return nil
}
return try? serializationService.deserialize(integrationProvisioningDict)
}
@discardableResult
func setIntegrationProvisioning(enabled: Bool,
success: @escaping () -> Void,
failure: @escaping (Error?) -> Void)
-> MXHTTPOperation? {
// Update only the "widgets" field in the account data
var integrationProvisioningDict = getAccountData(forEventType: Settings.integrationProvisioning) ?? [:]
integrationProvisioningDict[RiotSettingIntegrationProvisioning.CodingKeys.enabled.rawValue] = enabled
return session.setAccountData(integrationProvisioningDict, forType: Settings.integrationProvisioning, success: success, failure: failure)
}
// MARK: Allowed widgets
func permission(for widget: Widget) -> WidgetPermission {
guard let allowedWidgets = getAllowedWidgets() else {
return .undefined
}
if let value = allowedWidgets.widgets[widget.widgetEvent.eventId] {
return value == true ? .granted : .declined
} else {
return .undefined
}
}
func getAllowedWidgets() -> RiotSettingAllowedWidgets? {
guard let allowedWidgetsDict = getAccountData(forEventType: Settings.allowedWidgets) else {
return nil
}
return try? serializationService.deserialize(allowedWidgetsDict)
}
@discardableResult
func setPermission(_ permission: WidgetPermission,
for widget: Widget,
success: @escaping () -> Void,
failure: @escaping (Error?) -> Void)
-> MXHTTPOperation? {
guard let widgetEventId = widget.widgetEvent.eventId else {
return nil
}
var widgets = getAllowedWidgets()?.widgets ?? [:]
switch permission {
case .undefined:
widgets.removeValue(forKey: widgetEventId)
case .granted:
widgets[widgetEventId] = true
case .declined:
widgets[widgetEventId] = false
}
// Update only the "widgets" field in the account data
var allowedWidgetsDict = getAccountData(forEventType: Settings.allowedWidgets) ?? [:]
allowedWidgetsDict[RiotSettingAllowedWidgets.CodingKeys.widgets.rawValue] = widgets
return session.setAccountData(allowedWidgetsDict, forType: Settings.allowedWidgets, success: success, failure: failure)
}
// MARK: Allowed native widgets
/// Get the permission for widget that will be displayed natively instead within
/// a webview.
///
/// - Parameters:
/// - widget: the widget
/// - url: the url the native implementation will open. Nil will use the url declared in the widget
/// - Returns: the permission
func permission(forNative widget: Widget, fromUrl url: URL? = nil) -> WidgetPermission {
guard let allowedWidgets = getAllowedWidgets() else {
return .undefined
}
guard let type = widget.type, let domain = domainForNativeWidget(widget, fromUrl: url) else {
return .undefined
}
if let value = allowedWidgets.nativeWidgets[type]?[domain] {
return value == true ? .granted : .declined
} else {
return .undefined
}
}
/// Set the permission for widget that is displayed natively.
///
/// - Parameters:
/// - permission: the permission to set
/// - widget: the widget
/// - url: the url the native implementation opens. Nil will use the url declared in the widget
/// - success: the success block
/// - failure: the failure block
/// - Returns: a `MXHTTPOperation` instance.
@discardableResult
func setPermission(_ permission: WidgetPermission,
forNative widget: Widget,
fromUrl url: URL?,
success: @escaping () -> Void,
failure: @escaping (Error?) -> Void)
-> MXHTTPOperation? {
guard let type = widget.type, let domain = domainForNativeWidget(widget, fromUrl: url) else {
return nil
}
var nativeWidgets = getAllowedWidgets()?.nativeWidgets ?? [String: [String: Bool]]()
var nativeWidgetsType = nativeWidgets[type] ?? [String: Bool]()
switch permission {
case .undefined:
nativeWidgetsType.removeValue(forKey: domain)
case .granted:
nativeWidgetsType[domain] = true
case .declined:
nativeWidgetsType[domain] = false
}
nativeWidgets[type] = nativeWidgetsType
// Update only the "native_widgets" field in the account data
var allowedWidgetsDict = getAccountData(forEventType: Settings.allowedWidgets) ?? [:]
allowedWidgetsDict[RiotSettingAllowedWidgets.CodingKeys.nativeWidgets.rawValue] = nativeWidgets
return session.setAccountData(allowedWidgetsDict, forType: Settings.allowedWidgets, success: success, failure: failure)
}
// MARK: - Private
private func getAccountData(forEventType eventType: String) -> [String: Any]? {
return session.accountData.accountData(forEventType: eventType) as? [String: Any]
}
private func domainForNativeWidget(_ widget: Widget, fromUrl url: URL? = nil) -> String? {
var widgetUrl: URL?
if let widgetUrlString = widget.url {
widgetUrl = URL(string: widgetUrlString)
}
guard let url = url ?? widgetUrl, let domain = url.host else {
return nil
}
return domain
}
}

View file

@ -103,7 +103,7 @@
// Check if their scalar token must added
if ([[WidgetManager sharedManager] isScalarUrl:widgetUrl forUser:userId])
{
return [[WidgetManager sharedManager] getScalarTokenForMXSession:_mxSession validate:NO success:^(NSString *scalarToken) {
return [[WidgetManager sharedManager] getScalarTokenForMXSession:_mxSession validate:YES success:^(NSString *scalarToken) {
// Add the user scalar token
widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@",
scalarToken]];

View file

@ -56,6 +56,7 @@ typedef enum : NSUInteger
WidgetManagerErrorCodeNotEnoughPower,
WidgetManagerErrorCodeCreationFailed,
WidgetManagerErrorCodeNoIntegrationsServerConfigured,
WidgetManagerErrorCodeDisabledIntegrationsServer,
WidgetManagerErrorCodeFailedToConnectToIntegrationsServer,
WidgetManagerErrorCodeTermsNotSigned
}

View file

@ -50,6 +50,9 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
// User id -> scalar token
NSMutableDictionary<NSString*, WidgetManagerConfig*> *configs;
// User id -> MXSession
NSMutableDictionary<NSString*, MXSession*> *matrixSessions;
}
@end
@ -73,6 +76,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
self = [super init];
if (self)
{
matrixSessions = [NSMutableDictionary dictionary];
widgetEventListener = [NSMutableDictionary dictionary];
successBlockForWidgetCreation = [NSMutableDictionary dictionary];
failureBlockForWidgetCreation = [NSMutableDictionary dictionary];
@ -265,6 +269,14 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
return nil;
}
RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:room.mxSession];
if (!sharedSettings.hasIntegrationProvisioningEnabled)
{
NSLog(@"[WidgetManager] createJitsiWidgetInRoom: Error: Disabled integration manager for user %@", userId);
failure(self.errorForDisabledIntegrationManager);
return nil;
}
// Build data for a jitsi widget
NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsi, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))];
@ -367,6 +379,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
{
__weak __typeof__(self) weakSelf = self;
matrixSessions[mxSession.matrixRestClient.credentials.userId] = mxSession;
NSString *hash = [NSString stringWithFormat:@"%p", mxSession];
id listener = [mxSession listenToEventsOfTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) {
@ -421,6 +435,12 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
- (void)removeMatrixSession:(MXSession *)mxSession
{
// Remove by value in a dict
for (NSString *key in [matrixSessions allKeysForObject:mxSession])
{
[matrixSessions removeObjectForKey:key];
}
// mxSession.myUser.userId and mxSession.matrixRestClient.credentials.userId may be nil here
// So, use a kind of hash value instead
NSString *hash = [NSString stringWithFormat:@"%p", mxSession];
@ -433,18 +453,59 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
[failureBlockForWidgetCreation removeObjectForKey:hash];
}
- (MXSession*)matrixSessionForUser:(NSString*)userId
{
return matrixSessions[userId];
}
- (void)deleteDataForUser:(NSString *)userId
{
[configs removeObjectForKey:userId];
[self saveConfigs];
}
#pragma mark - User integrations configuration
- (WidgetManagerConfig*)createWidgetManagerConfigForUser:(NSString*)userId
{
WidgetManagerConfig *config;
MXSession *session = [self matrixSessionForUser:userId];
// Find the integrations settings for the user
// First, look at matrix account
// TODO in another user story
// Then, try to the homeserver configuration
MXWellknownIntegrationsManager *integrationsManager = session.homeserverWellknown.integrations.managers.firstObject;
if (integrationsManager)
{
config = [[WidgetManagerConfig alloc] initWithApiUrl:integrationsManager.apiUrl uiUrl:integrationsManager.uiUrl];
}
else
{
// Fallback on app settings
config = [self createWidgetManagerConfigWithAppSettings];
}
return config;
}
- (WidgetManagerConfig*)createWidgetManagerConfigWithAppSettings
{
NSString *apiUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"];
NSString *uiUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsUiUrl"];
return [[WidgetManagerConfig alloc] initWithApiUrl:apiUrl uiUrl:uiUrl];
}
#pragma mark - Modular interface
- (WidgetManagerConfig*)configForUser:(NSString*)userId
{
// Return a default config by default
return configs[userId] ? configs[userId] : [WidgetManagerConfig new];
return configs[userId] ? configs[userId] : [self createWidgetManagerConfigForUser:userId];
}
- (BOOL)hasIntegrationManagerForUser:(NSString*)userId
@ -697,7 +758,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
NSLog(@"[WidgetManager] migrate scalarTokens to integrationManagerConfigs for %@", userId);
WidgetManagerConfig *config = [WidgetManagerConfig new];
WidgetManagerConfig *config = [self createWidgetManagerConfigWithAppSettings];
config.scalarToken = scalarToken;
configs[userId] = config;
@ -738,4 +799,11 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_no_integrations_server_configured", @"Vector", nil)}];
}
- (NSError*)errorForDisabledIntegrationManager
{
return [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeDisabledIntegrationsServer
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_integration_manager_disabled", @"Vector", nil)}];
}
@end

View file

@ -75,15 +75,6 @@ class WidgetManagerConfig: NSObject, NSCoding {
super.init()
}
override convenience init () {
// Use app settings as default
let apiUrl = UserDefaults.standard.object(forKey: "integrationsRestUrl") as? NSString
let uiUrl = UserDefaults.standard.object(forKey: "integrationsUiUrl") as? NSString
self.init(apiUrl: apiUrl, uiUrl: uiUrl)
}
/// MARK: - NSCoding
enum CodingKeys: String {

View file

@ -53,6 +53,9 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject {
// }
func present(from viewController: UIViewController, otherUserId: String, otherDeviceId: String, animated: Bool) {
NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present from \(viewController)")
let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
deviceVerificationCoordinator.delegate = self
viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
@ -62,6 +65,9 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject {
}
func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) {
NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present incoming verification from \(viewController)")
let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, incomingTransaction: incomingTransaction)
deviceVerificationCoordinator.delegate = self
viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil)
@ -73,7 +79,10 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject {
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
}
NSLog("[DeviceVerificationCoordinatorBridgePresenter] Dismiss")
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil

View file

@ -20,6 +20,7 @@ import Foundation
/// DeviceVerificationIncomingViewController view actions exposed to view model
enum DeviceVerificationIncomingViewAction {
case loadData
case accept
case cancel
}

View file

@ -74,6 +74,7 @@ final class DeviceVerificationIncomingViewController: UIViewController {
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewDidLayoutSubviews() {

View file

@ -50,8 +50,6 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM
self.deviceId = transaction.otherDeviceId
self.mediaManager = session.mediaManager
self.registerTransactionDidStateChangeNotification(transaction: transaction)
}
deinit {
@ -61,6 +59,8 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM
func process(viewAction: DeviceVerificationIncomingViewAction) {
switch viewAction {
case .loadData:
self.registerTransactionDidStateChangeNotification(transaction: transaction)
case .accept:
self.acceptIncomingDeviceVerification()
case .cancel:
@ -89,6 +89,10 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM
private func registerTransactionDidStateChangeNotification(transaction: MXIncomingSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXIncomingSASTransaction else {
@ -97,17 +101,20 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .loaded)
self.coordinatorDelegate?.deviceVerificationIncomingViewModel(self, didAcceptTransaction: self.transaction)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break

View file

@ -115,21 +115,22 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy
guard let transaction = notification.object as? MXOutgoingSASTransaction else {
return
}
self.unregisterTransactionDidStateChangeNotification()
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.unregisterTransactionDidStateChangeNotification()
self.coordinatorDelegate?.deviceVerificationStartViewModel(self, didCompleteWithOutgoingTransaction: transaction)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break

View file

@ -20,6 +20,7 @@ import Foundation
/// DeviceVerificationVerifyViewController view actions exposed to view model
enum DeviceVerificationVerifyViewAction {
case loadData
case confirm
case complete
case cancel

View file

@ -70,6 +70,7 @@ final class DeviceVerificationVerifyViewController: UIViewController {
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewWillAppear(_ animated: Bool) {

View file

@ -41,8 +41,6 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel
self.transaction = transaction
self.emojis = self.transaction.sasEmoji
self.decimal = self.transaction.sasDecimal
self.registerTransactionDidStateChangeNotification(transaction: transaction)
}
deinit {
@ -52,6 +50,8 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel
func process(viewAction: DeviceVerificationVerifyViewAction) {
switch viewAction {
case .loadData:
self.registerTransactionDidStateChangeNotification(transaction: transaction)
case .confirm:
self.confirmTransaction()
case .complete:
@ -83,6 +83,10 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel
private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXSASTransaction else {
@ -91,27 +95,26 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel
switch transaction.state {
case MXSASTransactionStateVerified:
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .loaded)
self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidComplete(self)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateError:
guard let error = transaction.error else {
return
}
self.unregisterTransactionDidStateChangeNotification()
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.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break

View file

@ -38,6 +38,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
}
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic) BOOL isViewAppearedOnce;
@end
@ -69,15 +70,26 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
operation = nil;
}
- (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidLoad];
[self loadData];
[super viewWillAppear:animated];
if (!self.isViewAppearedOnce)
{
self.isViewAppearedOnce = YES;
[self loadData];
}
}
- (void)loadData
{
RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:mxSession];
if (!sharedSettings.hasIntegrationProvisioningEnabled)
{
[self showDisabledIntegrationManagerError];
return;
}
if (!self.URL && !operation)
{
[self startActivityIndicator];
@ -697,6 +709,33 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
}
#pragma mark - Widget Permission
- (void)checkWidgetPermissionWithCompletion:(void (^)(BOOL granted))completion
{
// The integration manager widget has its own terms
completion(YES);
}
#pragma mark - Disabled Integrations
- (void)showDisabledIntegrationManagerError
{
NSString *message = NSLocalizedStringFromTable(@"widget_integration_manager_disabled", @"Vector", nil);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self withdrawViewControllerAnimated:YES completion:nil];
}]];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Service terms
- (void)presentTerms
@ -732,4 +771,12 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self withdrawViewControllerAnimated:YES completion:nil];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -0,0 +1,189 @@
<?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="3Nb-ba-XcY">
<device id="retina6_1" 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>
<!--Widget Permission View Controller-->
<scene sceneID="hVk-V5-GY4">
<objects>
<viewController id="3Nb-ba-XcY" customClass="WidgetPermissionViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Z4L-Ka-Saj">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yPm-GN-iNf">
<rect key="frame" x="0.0" y="44" width="414" height="852"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vsY-4j-uQz">
<rect key="frame" x="0.0" y="0.0" width="414" height="421.5"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F2c-As-8Ce">
<rect key="frame" x="364" y="20" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="g3s-jh-fkQ"/>
<constraint firstAttribute="height" constant="30" id="qvd-zA-8uA"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<state key="normal" image="close_button"/>
<connections>
<action selector="closeButtonAction:" destination="3Nb-ba-XcY" eventType="touchUpInside" id="qJ1-fx-suE"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Load Widget" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4jF-pR-0xn">
<rect key="frame" x="20" y="24.5" width="334" height="21.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This widget was added by:" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iUc-92-5Iv">
<rect key="frame" x="20" y="64" width="374" height="18"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i5q-8U-NpS">
<rect key="frame" x="20" y="102" width="374" height="54"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UvZ-W0-l9O" customClass="MXKImageView">
<rect key="frame" x="0.0" y="7" width="40" height="40"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="CsC-a2-xHm"/>
<constraint firstAttribute="width" constant="40" id="f9t-fq-fc8"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="niK-55-mG3">
<rect key="frame" x="50" y="0.0" width="324" height="54"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="azre azer azer azer azer aezr azer azer zaer ae" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FjU-fb-8u1">
<rect key="frame" x="0.0" y="0.0" width="324" height="36"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="@paul:matrix.org" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Y8T-5r-qPI">
<rect key="frame" x="0.0" y="36" width="324" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="UvZ-W0-l9O" firstAttribute="leading" secondItem="i5q-8U-NpS" secondAttribute="leading" id="2Os-M9-Pld"/>
<constraint firstItem="niK-55-mG3" firstAttribute="top" relation="greaterThanOrEqual" secondItem="i5q-8U-NpS" secondAttribute="top" id="5NQ-1c-JIi"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="UvZ-W0-l9O" secondAttribute="bottom" id="LhM-Ik-Cag"/>
<constraint firstAttribute="height" priority="250" id="U78-dc-mnC"/>
<constraint firstItem="niK-55-mG3" firstAttribute="leading" secondItem="UvZ-W0-l9O" secondAttribute="trailing" constant="10" id="VUY-T5-xMd"/>
<constraint firstItem="UvZ-W0-l9O" firstAttribute="top" relation="greaterThanOrEqual" secondItem="i5q-8U-NpS" secondAttribute="top" id="Whs-DN-c3I"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="niK-55-mG3" secondAttribute="bottom" id="m6M-rS-dt4"/>
<constraint firstItem="UvZ-W0-l9O" firstAttribute="centerY" secondItem="i5q-8U-NpS" secondAttribute="centerY" id="n5u-UV-Cw6"/>
<constraint firstItem="niK-55-mG3" firstAttribute="centerY" secondItem="UvZ-W0-l9O" secondAttribute="centerY" id="qLj-Te-QyY"/>
<constraint firstAttribute="trailing" secondItem="niK-55-mG3" secondAttribute="trailing" id="qeG-IV-RJa"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d3k-sH-pkd">
<rect key="frame" x="20" y="176" width="374" height="161.5"/>
<string key="text">Using it may set cookies and share data with widget.com:
• Your display name
• Your avatar URL
• Your user ID
• Your theme
• Room ID
• Widget ID</string>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0mL-Yj-ueq">
<rect key="frame" x="20" y="357.5" width="374" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCLoginButton"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="e3O-dh-ock"/>
<constraint firstAttribute="height" constant="44" id="vW7-2c-XUA"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Continue">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="continueButtonAction:" destination="3Nb-ba-XcY" eventType="touchUpInside" id="56l-uP-Q22"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="d3k-sH-pkd" firstAttribute="top" secondItem="i5q-8U-NpS" secondAttribute="bottom" constant="20" id="0zY-vK-dcB"/>
<constraint firstItem="0mL-Yj-ueq" firstAttribute="width" secondItem="d3k-sH-pkd" secondAttribute="width" priority="250" id="6aV-C7-iNC"/>
<constraint firstItem="iUc-92-5Iv" firstAttribute="top" secondItem="4jF-pR-0xn" secondAttribute="bottom" constant="18" id="96I-eP-mad"/>
<constraint firstItem="0mL-Yj-ueq" firstAttribute="top" secondItem="d3k-sH-pkd" secondAttribute="bottom" constant="20" id="9lC-9v-jhB"/>
<constraint firstAttribute="bottom" secondItem="0mL-Yj-ueq" secondAttribute="bottom" constant="20" id="Ae2-ca-Wj8"/>
<constraint firstItem="4jF-pR-0xn" firstAttribute="centerY" secondItem="F2c-As-8Ce" secondAttribute="centerY" id="EM7-qU-UJn"/>
<constraint firstItem="i5q-8U-NpS" firstAttribute="trailing" secondItem="iUc-92-5Iv" secondAttribute="trailing" id="KFE-YJ-23G"/>
<constraint firstAttribute="trailing" secondItem="d3k-sH-pkd" secondAttribute="trailing" constant="20" id="OVs-CR-hnR"/>
<constraint firstItem="i5q-8U-NpS" firstAttribute="leading" secondItem="iUc-92-5Iv" secondAttribute="leading" id="Oid-gA-1Ul"/>
<constraint firstItem="iUc-92-5Iv" firstAttribute="leading" secondItem="4jF-pR-0xn" secondAttribute="leading" id="cUc-9r-Z6J"/>
<constraint firstItem="iUc-92-5Iv" firstAttribute="trailing" secondItem="F2c-As-8Ce" secondAttribute="trailing" id="e64-iy-oYE"/>
<constraint firstItem="i5q-8U-NpS" firstAttribute="top" secondItem="iUc-92-5Iv" secondAttribute="bottom" constant="20" id="fEi-E8-VEb"/>
<constraint firstItem="d3k-sH-pkd" firstAttribute="leading" secondItem="vsY-4j-uQz" secondAttribute="leading" constant="20" id="fXW-MO-s6w"/>
<constraint firstItem="F2c-As-8Ce" firstAttribute="top" secondItem="vsY-4j-uQz" secondAttribute="top" constant="20" id="iKL-c9-I8u"/>
<constraint firstItem="F2c-As-8Ce" firstAttribute="leading" secondItem="4jF-pR-0xn" secondAttribute="trailing" constant="10" id="io2-qE-Xmg"/>
<constraint firstItem="0mL-Yj-ueq" firstAttribute="centerX" secondItem="vsY-4j-uQz" secondAttribute="centerX" id="tLS-tp-y26"/>
<constraint firstItem="0mL-Yj-ueq" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vsY-4j-uQz" secondAttribute="leading" constant="20" id="tXE-bz-MoB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="0mL-Yj-ueq" secondAttribute="trailing" constant="20" id="xHm-ec-RiN"/>
<constraint firstAttribute="trailing" secondItem="F2c-As-8Ce" secondAttribute="trailing" constant="20" id="y9f-Kq-oYP"/>
<constraint firstItem="4jF-pR-0xn" firstAttribute="leading" secondItem="vsY-4j-uQz" secondAttribute="leading" constant="20" id="yyc-kc-was"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="vsY-4j-uQz" firstAttribute="top" secondItem="yPm-GN-iNf" secondAttribute="top" id="8Ca-HP-L6B"/>
<constraint firstAttribute="bottom" secondItem="vsY-4j-uQz" secondAttribute="bottom" id="bEQ-7N-MIg"/>
<constraint firstAttribute="trailing" secondItem="vsY-4j-uQz" secondAttribute="trailing" id="dJ0-Fd-abb"/>
<constraint firstItem="vsY-4j-uQz" firstAttribute="leading" secondItem="yPm-GN-iNf" secondAttribute="leading" id="eIi-U9-scV"/>
<constraint firstItem="vsY-4j-uQz" firstAttribute="width" secondItem="yPm-GN-iNf" secondAttribute="width" id="zZN-Jx-lhS"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="yPm-GN-iNf" secondAttribute="bottom" id="AVK-pe-mSa"/>
<constraint firstAttribute="trailing" secondItem="yPm-GN-iNf" secondAttribute="trailing" id="Pm4-AI-Yah"/>
<constraint firstItem="yPm-GN-iNf" firstAttribute="leading" secondItem="Z4L-Ka-Saj" secondAttribute="leading" id="Spn-UK-HMu"/>
<constraint firstItem="yPm-GN-iNf" firstAttribute="top" secondItem="0Vv-Fe-78X" secondAttribute="top" id="qkB-1D-obj"/>
</constraints>
<viewLayoutGuide key="safeArea" id="0Vv-Fe-78X"/>
</view>
<connections>
<outlet property="closeButton" destination="F2c-As-8Ce" id="dJz-ii-CtQ"/>
<outlet property="continueButton" destination="0mL-Yj-ueq" id="TlH-WR-YZc"/>
<outlet property="creatorAvatarImageView" destination="UvZ-W0-l9O" id="GcL-Cf-bKe"/>
<outlet property="creatorDisplayNameLabel" destination="FjU-fb-8u1" id="CnD-hH-yax"/>
<outlet property="creatorInfoTitleLabel" destination="iUc-92-5Iv" id="YB7-M7-v5p"/>
<outlet property="creatorUserIDLabel" destination="Y8T-5r-qPI" id="gB0-wH-0Vg"/>
<outlet property="informationLabel" destination="d3k-sH-pkd" id="LYA-Fi-nkb"/>
<outlet property="scrollView" destination="yPm-GN-iNf" id="c9d-T1-4hN"/>
<outlet property="titleLabel" destination="4jF-pR-0xn" id="6UD-Nz-fic"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="WZQ-Wb-Bbh" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-175" y="125"/>
</scene>
</scenes>
<resources>
<image name="close_button" width="16" height="16"/>
</resources>
</document>

View file

@ -0,0 +1,229 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
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
@objc
final class WidgetPermissionViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let continueButtonCornerRadius: CGFloat = 8.0
}
private enum Sizing {
static var viewController: WidgetPermissionViewController?
static var widthConstraint: NSLayoutConstraint?
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var closeButton: UIButton!
@IBOutlet private weak var creatorInfoTitleLabel: UILabel!
@IBOutlet private weak var creatorAvatarImageView: MXKImageView!
@IBOutlet private weak var creatorDisplayNameLabel: UILabel!
@IBOutlet private weak var creatorUserIDLabel: UILabel!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var continueButton: UIButton!
// MARK: Private
private var viewModel: WidgetPermissionViewModel! {
didSet {
self.updateViews()
}
}
private var theme: Theme!
// MARK: Public
@objc var didTapCloseButton: (() -> Void)?
@objc var didTapContinueButton: (() -> Void)?
// MARK: - Setup
@objc class func instantiate(with viewModel: WidgetPermissionViewModel) -> WidgetPermissionViewController {
let viewController = StoryboardScene.WidgetPermissionViewController.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.setupViews()
self.updateViews()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.scrollView.flashScrollIndicators()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.creatorAvatarImageView.layer.cornerRadius = self.creatorAvatarImageView.frame.size.width/2
self.continueButton.layer.cornerRadius = Constants.continueButtonCornerRadius
self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
self.titleLabel.textColor = theme.textPrimaryColor
self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal)
self.creatorInfoTitleLabel.textColor = theme.textSecondaryColor
self.creatorDisplayNameLabel.textColor = theme.textSecondaryColor
self.creatorUserIDLabel.textColor = theme.textSecondaryColor
self.informationLabel.textColor = theme.textSecondaryColor
self.continueButton.vc_setBackgroundColor(theme.tintColor, for: .normal)
}
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() {
self.closeButton.layer.masksToBounds = true
self.setupCreatorAvatarImageView()
self.titleLabel.text = VectorL10n.roomWidgetPermissionTitle
self.creatorInfoTitleLabel.text = VectorL10n.roomWidgetPermissionCreatorInfoTitle
self.informationLabel.text = ""
self.setupContinueButton()
}
private func updateViews() {
if let avatarImageView = self.creatorAvatarImageView {
let defaultavatarImage = AvatarGenerator.generateAvatar(forMatrixItem: self.viewModel.creatorUserId, withDisplayName: self.viewModel.creatorDisplayName)
avatarImageView.setImageURI(self.viewModel.creatorAvatarUrl, withType: nil, andImageOrientation: .up, previewImage: defaultavatarImage, mediaManager: self.viewModel.mediaManager)
}
if let creatorDisplayNameLabel = self.creatorDisplayNameLabel {
if let creatorDisplayName = self.viewModel.creatorDisplayName {
creatorDisplayNameLabel.text = creatorDisplayName
} else {
creatorDisplayNameLabel.isHidden = true
}
}
if let creatorUserIDLabel = self.creatorUserIDLabel {
creatorUserIDLabel.text = self.viewModel.creatorUserId
}
if let informationLabel = self.informationLabel {
informationLabel.text = self.viewModel.permissionsInformationText
}
}
private func setupCreatorAvatarImageView() {
self.creatorAvatarImageView.defaultBackgroundColor = UIColor.clear
self.creatorAvatarImageView.enableInMemoryCache = true
self.creatorAvatarImageView.clipsToBounds = true
}
private func setupContinueButton() {
self.continueButton.layer.masksToBounds = true
self.continueButton.setTitle(VectorL10n.continue, for: .normal)
}
// MARK: - Actions
@IBAction private func closeButtonAction(_ sender: Any) {
self.didTapCloseButton?()
}
@IBAction private func continueButtonAction(_ sender: Any) {
self.didTapContinueButton?()
}
}
// MARK: - SlidingModalPresentable
extension WidgetPermissionViewController: SlidingModalPresentable {
func allowsDismissOnBackgroundTap() -> Bool {
return false
}
func layoutHeightFittingWidth(_ width: CGFloat) -> CGFloat {
let sizingViewContoller: WidgetPermissionViewController
if let viewController = WidgetPermissionViewController.Sizing.viewController {
viewController.viewModel = self.viewModel
sizingViewContoller = viewController
} else {
sizingViewContoller = WidgetPermissionViewController.instantiate(with: self.viewModel)
WidgetPermissionViewController.Sizing.viewController = sizingViewContoller
}
let sizingViewContollerView: UIView = sizingViewContoller.view
if let widthConstraint = WidgetPermissionViewController.Sizing.widthConstraint {
widthConstraint.constant = width
} else {
let widthConstraint = sizingViewContollerView.widthAnchor.constraint(equalToConstant: width)
widthConstraint.isActive = true
WidgetPermissionViewController.Sizing.widthConstraint = widthConstraint
sizingViewContollerView.heightAnchor.constraint(equalToConstant: 0)
}
sizingViewContollerView.layoutIfNeeded()
return sizingViewContoller.scrollView.contentSize.height
}
}

View file

@ -0,0 +1,68 @@
/*
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
/// View model used by `WidgetPermissionViewController`
@objcMembers
final class WidgetPermissionViewModel: NSObject {
// MARK: - Properties
let creatorUserId: String
let creatorDisplayName: String?
let creatorAvatarUrl: String?
let widgetDomain: String?
let isWebviewWidget: Bool
let widgetPermissions: [String]
let mediaManager: MXMediaManager
lazy var permissionsInformationText: String = {
return self.buildPermissionsInformationText()
}()
// MARK: - Setup
init(creatorUserId: String, creatorDisplayName: String?, creatorAvatarUrl: String?, widgetDomain: String?, isWebviewWidget: Bool, widgetPermissions: [String], mediaManager: MXMediaManager) {
self.creatorUserId = creatorUserId
self.creatorDisplayName = creatorDisplayName
self.creatorAvatarUrl = creatorAvatarUrl
self.widgetDomain = widgetDomain
self.isWebviewWidget = isWebviewWidget
self.widgetPermissions = widgetPermissions
self.mediaManager = mediaManager
}
// MARK: - Private
private func buildPermissionsInformationText() -> String {
let informationTitle: String
let widgetDomain = self.widgetDomain ?? ""
if self.isWebviewWidget {
informationTitle = VectorL10n.roomWidgetPermissionWebviewInformationTitle(widgetDomain)
} else {
informationTitle = VectorL10n.roomWidgetPermissionInformationTitle(widgetDomain)
}
let permissionsList = self.widgetPermissions.reduce("") { (accumulatedPermissions, permission) -> String in
return accumulatedPermissions + "\n\(permission)"
}
return informationTitle + permissionsList
}
}

View file

@ -21,13 +21,19 @@
#import "WidgetManager.h"
#import "WidgetViewController.h"
#import "IntegrationManagerViewController.h"
#import "Riot-Swift.h"
@interface WidgetPickerViewController ()
@interface WidgetPickerViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
{
MXSession *mxSession;
NSString *roomId;
}
@property (nonatomic, weak) UIViewController *presentingViewController;
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic, strong) MXKRoomDataSource *roomDataSource;
@property (nonatomic, strong) Widget *selectedWidget;
@end
@implementation WidgetPickerViewController
@ -67,27 +73,14 @@
{
// Hide back button title
mxkViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
// Display the widget
[widget widgetUrl:^(NSString * _Nonnull widgetUrl) {
WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget];
widgetVC.roomDataSource = roomDataSource;
[mxkViewController.navigationController pushViewController:widgetVC animated:YES];
} failure:^(NSError * _Nonnull error) {
NSLog(@"[WidgetPickerVC] Cannot display widget %@", widget);
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
[self fetchWidgetURLAndDisplayUsingWidget:widget canPresentServiceTerms:YES];
}];
[self.alertController addAction:alertAction];
}
// Link to the integration manager
alertAction = [UIAlertAction actionWithTitle:@"Manage integrations..."
alertAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"widget_picker_manage_integrations", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action)
{
@ -107,8 +100,88 @@
[self.alertController addAction:alertAction];
// And show it
[mxkViewController presentViewController:_alertController animated:YES completion:nil];
[mxkViewController presentViewController:self.alertController animated:YES completion:nil];
self.presentingViewController = mxkViewController;
}];
}
- (void)fetchWidgetURLAndDisplayUsingWidget:(Widget*)widget canPresentServiceTerms:(BOOL)canPresentServiceTerms
{
[widget widgetUrl:^(NSString * _Nonnull widgetUrl) {
// Display the widget
WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget];
widgetVC.roomDataSource = self.roomDataSource;
[self.presentingViewController.navigationController pushViewController:widgetVC animated:YES];
} failure:^(NSError * _Nonnull error) {
NSLog(@"[WidgetPickerVC] Get widget URL failed with error: %@", error);
if (canPresentServiceTerms
&& [error.domain isEqualToString:WidgetManagerErrorDomain]
&& error.code == WidgetManagerErrorCodeTermsNotSigned)
{
[self presentTermsForWidget:widget];
}
else
{
[[AppDelegate theDelegate] showErrorAsAlert:error];
}
}];
}
#pragma mark - Service terms
- (void)presentTermsForWidget:(Widget*)widget
{
if (self.serviceTermsModalCoordinatorBridgePresenter)
{
return;
}
WidgetManagerConfig *config = [[WidgetManager sharedManager] configForUser:widget.mxSession.myUser.userId];
NSLog(@"[WidgetVC] presentTerms for %@", config.baseUrl);
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:widget.mxSession baseUrl:config.baseUrl
serviceType:MXServiceTypeIntegrationManager
outOfContext:NO
accessToken:config.scalarToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self.presentingViewController animated:YES];
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
MXWeakify(self);
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
MXStrongifyAndReturnIfNil(self);
if (self.selectedWidget)
{
[self fetchWidgetURLAndDisplayUsingWidget:self.selectedWidget canPresentServiceTerms:NO];
}
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -30,7 +30,11 @@ final class JitsiService: NSObject {
JMCallKitProxy.enabled = enableCallKit
}
}
var serverURL: URL? {
return self.jitsiMeet.defaultConferenceOptions?.serverURL
}
private let jitsiMeet = JitsiMeet.sharedInstance()
// MARK: - Setup

View file

@ -146,12 +146,26 @@ static const NSString *kJitsiDataErrorKey = @"error";
{
if (conferenceId)
{
// 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;
// Get info about the room and our user
MXSession *session = self.widget.mxSession;
MXRoomSummary *roomSummary = [session roomSummaryWithRoomId:self.widget.roomId];
MXRoom *room = [session roomWithRoomId:self.widget.roomId];
MXRoomMember *roomMember = [room.dangerousSyncState.members memberWithUserId:session.myUser.userId];
NSString *userDisplayName = roomMember.displayname;
NSString *avatar = [session.mediaManager urlOfContent:roomMember.avatarUrl];
NSURL *avatarUrl = [NSURL URLWithString:avatar];
JitsiMeetConferenceOptions *jitsiMeetConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder * _Nonnull builder) {
builder.room = conferenceId;
builder.videoMuted = !self.startWithVideo;
builder.subject = roomSummary.displayname;
builder.userInfo = [[JitsiMeetUserInfo alloc] initWithDisplayName:userDisplayName
andEmail:nil
andAvatar:avatarUrl];
}];
[self.jitsiMeetView join:jitsiMeetConferenceOptions];

View file

@ -26,6 +26,9 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
@interface WidgetViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic, strong) NSString *widgetUrl;
@property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter;
@end
@ -34,9 +37,12 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
- (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)theWidget
{
self = [super initWithURL:widgetUrl];
// The opening of the url is delayed in viewWillAppear where we will check
// the widget permission
self = [super initWithURL:nil];
if (self)
{
self.widgetUrl = widgetUrl;
widget = theWidget;
}
return self;
@ -54,6 +60,74 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
if (widget)
{
self.navigationItem.title = widget.name ? widget.name : widget.type;
UIBarButtonItem *menuButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"room_context_menu_more"] style:UIBarButtonItemStylePlain target:self action:@selector(onMenuButtonPressed:)];
self.navigationItem.rightBarButtonItem = menuButton;
}
self.slidingModalPresenter = [SlidingModalPresenter new];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Check widget permission before opening the widget
[self checkWidgetPermissionWithCompletion:^(BOOL granted) {
[self.slidingModalPresenter dismissWithAnimated:YES completion:nil];
if (granted)
{
self.URL = self.widgetUrl;
}
else
{
[self withdrawViewControllerAnimated:YES completion:nil];
}
}];
}
- (void)reloadWidget
{
self.URL = self.widgetUrl;
}
- (BOOL)hasUserEnoughPowerToManageCurrentWidget
{
BOOL hasUserEnoughPower = NO;
MXSession *session = widget.mxSession;
MXRoom *room = [session roomWithRoomId:self.widget.roomId];
MXRoomState *roomState = room.dangerousSyncState;
if (roomState)
{
// Check user's power in the room
MXRoomPowerLevels *powerLevels = roomState.powerLevels;
NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:session.myUser.userId];
// The user must be able to send state events to manage widgets
if (oneSelfPowerLevel >= powerLevels.stateDefault)
{
hasUserEnoughPower = YES;
}
}
return hasUserEnoughPower;
}
- (void)removeCurrentWidget
{
WidgetManager *widgetManager = [WidgetManager sharedManager];
MXRoom *room = [self.widget.mxSession roomWithRoomId:self.widget.roomId];
NSString *widgetId = self.widget.widgetId;
if (room && widgetId)
{
[widgetManager closeWidget:widgetId inRoom:room success:^{
} failure:^(NSError *error) {
NSLog(@"[WidgetVC] removeCurrentWidget failed. Error: %@", error);
}];
}
}
@ -94,6 +168,182 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Widget Permission
- (void)checkWidgetPermissionWithCompletion:(void (^)(BOOL granted))completion
{
MXSession *session = widget.mxSession;
if ([widget.widgetEvent.sender isEqualToString:session.myUser.userId])
{
// No need of more permission check if the user created the widget
completion(YES);
return;
}
// Check permission in user Riot settings
__block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session];
WidgetPermission permission = [sharedSettings permissionFor:widget];
if (permission == WidgetPermissionGranted)
{
completion(YES);
}
else
{
// Note: ask permission again if the user previously declined it
[self askPermissionWithCompletion:^(BOOL granted) {
// Update the settings in user account data in parallel
[sharedSettings setPermission:granted ? WidgetPermissionGranted : WidgetPermissionDeclined
for:self.widget
success:^
{
sharedSettings = nil;
}
failure:^(NSError * _Nullable error)
{
NSLog(@"[WidgetVC] setPermissionForWidget failed. Error: %@", error);
sharedSettings = nil;
}];
completion(granted);
}];
}
}
- (void)askPermissionWithCompletion:(void (^)(BOOL granted))completion
{
NSString *widgetCreatorUserId = self.widget.widgetEvent.sender ?: NSLocalizedStringFromTable(@"room_participants_unknown", @"Vector", nil);
MXSession *session = widget.mxSession;
MXRoom *room = [session roomWithRoomId:self.widget.widgetEvent.roomId];
MXRoomState *roomState = room.dangerousSyncState;
MXRoomMember *widgetCreatorRoomMember = [roomState.members memberWithUserId:widgetCreatorUserId];
NSString *widgetDomain = @"";
if (widget.url)
{
NSString *host = [[NSURL alloc] initWithString:widget.url].host;
if (host)
{
widgetDomain = host;
}
}
MXMediaManager *mediaManager = widget.mxSession.mediaManager;
NSString *widgetCreatorDisplayName = widgetCreatorRoomMember.displayname;
NSString *widgetCreatorAvatarURL = widgetCreatorRoomMember.avatarUrl;
NSArray<NSString*> *permissionStrings = @[
NSLocalizedStringFromTable(@"room_widget_permission_display_name_permission", @"Vector", nil),
NSLocalizedStringFromTable(@"room_widget_permission_avatar_url_permission", @"Vector", nil),
NSLocalizedStringFromTable(@"room_widget_permission_user_id_permission", @"Vector", nil),
NSLocalizedStringFromTable(@"room_widget_permission_theme_permission", @"Vector", nil),
NSLocalizedStringFromTable(@"room_widget_permission_widget_id_permission", @"Vector", nil),
NSLocalizedStringFromTable(@"room_widget_permission_room_id_permission", @"Vector", nil)
];
WidgetPermissionViewModel *widgetPermissionViewModel = [[WidgetPermissionViewModel alloc] initWithCreatorUserId:widgetCreatorUserId
creatorDisplayName:widgetCreatorDisplayName creatorAvatarUrl:widgetCreatorAvatarURL widgetDomain:widgetDomain
isWebviewWidget:YES
widgetPermissions:permissionStrings
mediaManager:mediaManager];
WidgetPermissionViewController *widgetPermissionViewController = [WidgetPermissionViewController instantiateWith:widgetPermissionViewModel];
widgetPermissionViewController.didTapContinueButton = ^{
completion(YES);
};
widgetPermissionViewController.didTapCloseButton = ^{
completion(NO);
};
[self.slidingModalPresenter present:widgetPermissionViewController from:self animated:YES completion:nil];
}
- (void)revokePermissionForCurrentWidget
{
MXSession *session = widget.mxSession;
__block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session];
[sharedSettings setPermission:WidgetPermissionDeclined for:widget success:^{
sharedSettings = nil;
} failure:^(NSError * _Nullable error) {
NSLog(@"[WidgetVC] revokePermissionForCurrentWidget failed. Error: %@", error);
sharedSettings = nil;
}];
}
#pragma mark - Contextual Menu
- (IBAction)onMenuButtonPressed:(id)sender
{
[self showMenu];
}
-(void)showMenu
{
MXSession *session = widget.mxSession;
UIAlertController *menu = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[menu addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"widget_menu_refresh", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[self reloadWidget];
}]];
NSURL *url = [NSURL URLWithString:self.widgetUrl];
if (url && [[UIApplication sharedApplication] canOpenURL:url])
{
[menu addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"widget_menu_open_outside", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {
}];
}]];
}
if (![widget.widgetEvent.sender isEqualToString:session.myUser.userId])
{
[menu addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"widget_menu_revoke_permission", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[self revokePermissionForCurrentWidget];
[self withdrawViewControllerAnimated:YES completion:nil];
}]];
}
if ([self hasUserEnoughPowerToManageCurrentWidget])
{
[menu addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"widget_menu_remove", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[self removeCurrentWidget];
[self withdrawViewControllerAnimated:YES completion:nil];
}]];
}
[menu addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
}]];
[self presentViewController:menu animated:YES completion:nil];
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
@ -435,4 +685,12 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self withdrawViewControllerAnimated:YES completion:nil];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -228,6 +228,7 @@
@property (nonatomic, strong) ReactionHistoryCoordinatorBridgePresenter *reactionHistoryCoordinatorBridgePresenter;
@property (nonatomic, strong) CameraPresenter *cameraPresenter;
@property (nonatomic, strong) MediaPickerCoordinatorBridgePresenter *mediaPickerPresenter;
@property (nonatomic, strong) RoomMessageURLParser *roomMessageURLParser;
@end
@ -423,6 +424,7 @@
self.roomContextualMenuPresenter = [RoomContextualMenuPresenter new];
self.errorPresenter = [MXKErrorAlertPresentation new];
self.roomMessageURLParser = [RoomMessageURLParser new];
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -2915,6 +2917,13 @@
// Retrieve the type of interaction expected with the URL (See UITextItemInteraction)
NSNumber *urlItemInteractionValue = userInfo[kMXKRoomBubbleCellUrlItemInteraction];
RoomMessageURLType roomMessageURLType = RoomMessageURLTypeUnknown;
if (url)
{
roomMessageURLType = [self.roomMessageURLParser parseURL:url];
}
// When a link refers to a room alias/id, a user id or an event id, the non-ASCII characters (like '#' in room alias) has been escaped
// to be able to convert it into a legal URL string.
NSString *absoluteURLString = [url.absoluteString stringByRemovingPercentEncoding];
@ -3007,19 +3016,40 @@
// Fallback case for external links
switch (urlItemInteractionValue.integerValue) {
case UITextItemInteractionInvokeDefaultAction:
{
[[UIApplication sharedApplication] vc_open:url completionHandler:^(BOOL success) {
if (!success)
{
[self showUnableToOpenLinkErrorAlert];
}
}];
shouldDoAction = NO;
{
switch (roomMessageURLType) {
case RoomMessageURLTypeAppleDataDetector:
// Keep the default OS behavior on single tap when UITextView data detector detect a known type.
shouldDoAction = YES;
break;
case RoomMessageURLTypeDummy:
// Do nothing for dummy links
shouldDoAction = NO;
break;
default:
// Try to open the link
[[UIApplication sharedApplication] vc_open:url completionHandler:^(BOOL success) {
if (!success)
{
[self showUnableToOpenLinkErrorAlert];
}
}];
shouldDoAction = NO;
break;
}
}
break;
case UITextItemInteractionPresentActions:
{
// Long press on link, present room contextual menu.
// Retrieve the tapped event
MXEvent *tappedEvent = userInfo[kMXKRoomBubbleCellEventKey];
if (tappedEvent)
{
// Long press on link, present room contextual menu.
[self showContextualMenuForEvent:tappedEvent fromSingleTapGesture:NO cell:cell animated:YES];
}
shouldDoAction = NO;
}
break;

View file

@ -224,11 +224,23 @@ final class ServiceTermsModalScreenViewController: UIViewController {
guard let policyIndex = sender.view?.tag else {
return
}
let isCheckBoxSelected: Bool
if self.checkedPolicies.contains(policyIndex) {
self.checkedPolicies.remove(policyIndex)
isCheckBoxSelected = false
} else {
checkedPolicies.insert(policyIndex)
isCheckBoxSelected = true
}
if let checkBoxImageView = sender.view as? UIImageView {
if isCheckBoxSelected {
checkBoxImageView.accessibilityTraits.insert(.selected)
} else {
checkBoxImageView.accessibilityTraits.remove(.selected)
}
}
self.refreshViews()
@ -275,6 +287,11 @@ extension ServiceTermsModalScreenViewController: UITableViewDataSource {
checkBox.isUserInteractionEnabled = true
checkBox.tag = indexPath.row
checkBox.addGestureRecognizer(gesture)
checkBox.isAccessibilityElement = true
checkBox.accessibilityTraits = .button
checkBox.accessibilityLabel = VectorL10n.accessibilityCheckboxLabel
checkBox.accessibilityHint = VectorL10n.serviceTermsModalPolicyCheckboxAccessibilityHint(policy.name)
}
return cell

View file

@ -57,6 +57,7 @@ enum
SETTINGS_SECTION_IDENTITY_SERVER_INDEX,
SETTINGS_SECTION_CONTACTS_INDEX,
SETTINGS_SECTION_IGNORED_USERS_INDEX,
SETTINGS_SECTION_INTEGRATIONS_INDEX,
SETTINGS_SECTION_USER_INTERFACE_INDEX,
SETTINGS_SECTION_ADVANCED_INDEX,
SETTINGS_SECTION_OTHER_INDEX,
@ -94,6 +95,13 @@ enum
CALLS_COUNT
};
enum
{
INTEGRATIONS_INDEX,
INTEGRATIONS_DESCRIPTION_INDEX,
INTEGRATIONS_COUNT
};
enum
{
USER_INTERFACE_LANGUAGE_INDEX = 0,
@ -129,7 +137,10 @@ enum
LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0,
LABS_USE_JITSI_WIDGET_INDEX,
LABS_CRYPTO_INDEX,
LABS_COUNT
LABS_COUNT, // TODO: Remove it once features exist
LABS_DM_KEY_VERIFICATION_INDEX,
LABS_CROSS_SIGNING_INDEX,
// LABS_COUNT
};
enum {
@ -1390,6 +1401,10 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
{
count = IDENTITY_SERVER_COUNT;
}
else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX)
{
count = INTEGRATIONS_COUNT;
}
else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX)
{
count = USER_INTERFACE_COUNT;
@ -2057,6 +2072,44 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
break;
}
}
else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX)
{
switch (row) {
case INTEGRATIONS_INDEX:
{
RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session];
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_integrations_allow_button", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = sharedSettings.hasIntegrationProvisioningEnabled;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
labelAndSwitchCell.mxkSwitch.enabled = YES;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleAllowIntegrations:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell;
break;
}
case INTEGRATIONS_DESCRIPTION_INDEX:
{
MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView];
NSString *integrationManager = [WidgetManager.sharedManager configForUser:session.myUser.userId].apiUrl;
NSString *integrationManagerDomain = [NSURL URLWithString:integrationManager].host;
NSString *description = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_integrations_allow_description", @"Vector", nil), integrationManagerDomain];
descriptionCell.textLabel.text = description;
descriptionCell.textLabel.numberOfLines = 0;
descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone;
cell = descriptionCell;
break;
}
default:
break;
}
}
else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX)
{
if (row == USER_INTERFACE_LANGUAGE_INDEX)
@ -2395,6 +2448,30 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
labelAndSwitchCell.mxkSwitch.enabled = NO;
}
cell = labelAndSwitchCell;
}
else if (row == LABS_DM_KEY_VERIFICATION_INDEX)
{
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_dm_key_verification", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableDMKeyVerification;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsDMKeyVerification:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell;
}
else if (row == LABS_CROSS_SIGNING_INDEX)
{
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_cross_signing", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableCrossSigning;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsCrossSigning:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell;
}
}
@ -2564,6 +2641,10 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
{
return NSLocalizedStringFromTable(@"settings_identity_server_settings", @"Vector", nil);
}
else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX)
{
return NSLocalizedStringFromTable(@"settings_integrations", @"Vector", nil);
}
else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX)
{
return NSLocalizedStringFromTable(@"settings_user_interface", @"Vector", nil);
@ -3187,6 +3268,24 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
self.mainSession.callManager.fallbackSTUNServer = RiotSettings.shared.allowStunServerFallback ? RiotSettings.shared.stunServerFallback : nil;
}
- (void)toggleAllowIntegrations:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
MXSession *session = self.mainSession;
[self startActivityIndicator];
__block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session];
[sharedSettings setIntegrationProvisioningWithEnabled:switchButton.on success:^{
sharedSettings = nil;
[self stopActivityIndicator];
} failure:^(NSError * _Nullable error) {
sharedSettings = nil;
[switchButton setOn:!switchButton.on animated:YES];
[self stopActivityIndicator];
}];
}
- (void)toggleShowDecodedContent:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
@ -3425,6 +3524,20 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
}
}
}
- (void)toggleLabsDMKeyVerification:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
RiotSettings.shared.enableDMKeyVerification = switchButton.isOn;
}
- (void)toggleLabsCrossSigning:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
RiotSettings.shared.enableCrossSigning = switchButton.isOn;
}
- (void)toggleBlacklistUnverifiedDevices:(id)sender
{

View file

@ -0,0 +1,165 @@
/*
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
import Reusable
protocol SlidingModalContainerViewDelegate: class {
func slidingModalContainerViewDidTapBackground(_ view: SlidingModalContainerView)
}
/// `SlidingModalContainerView` is a custom UIView used as a `UIViewControllerContextTransitioning.containerView` subview to embed a `SlidingModalPresentable` during presentation.
final class SlidingModalContainerView: UIView, Themable, NibLoadable {
// MARK: - Constants
private enum Constants {
static let cornerRadius: CGFloat = 12.0
static let dimmingColorAlpha: CGFloat = 0.7
}
private enum Sizing {
static let view = SlidingModalContainerView.loadFromNib()
static var widthConstraint: NSLayoutConstraint?
static var heightConstraint: NSLayoutConstraint?
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var dimmingView: UIView!
@IBOutlet private weak var contentView: UIView!
@IBOutlet private weak var contentViewHeightConstraint: NSLayoutConstraint!
@IBOutlet private weak var contentViewBottomConstraint: NSLayoutConstraint!
// MARK: Private
private var dismissContentViewBottomConstant: CGFloat {
let bottomSafeAreaHeight: CGFloat
if #available(iOS 11.0, *) {
bottomSafeAreaHeight = self.contentView.safeAreaInsets.bottom
} else {
bottomSafeAreaHeight = 0
}
return -(self.contentViewHeightConstraint.constant + bottomSafeAreaHeight)
}
// MARK: Public
var contentViewFrame: CGRect {
return self.contentView.frame
}
weak var delegate: SlidingModalContainerViewDelegate?
// MARK: - Setup
static func instantiate() -> SlidingModalContainerView {
return SlidingModalContainerView.loadFromNib()
}
// MARK: - Life cycle
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.layer.masksToBounds = true
self.dimmingView.backgroundColor = UIColor.black.withAlphaComponent(Constants.dimmingColorAlpha)
self.setupBackgroundTapGestureRecognizer()
self.update(theme: ThemeService.shared().theme)
}
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.layer.cornerRadius = Constants.cornerRadius
}
// MARK: - Public
func preparePresentAnimation() {
self.contentViewBottomConstraint.constant = 0
}
func prepareDismissAnimation() {
self.contentViewBottomConstraint.constant = self.dismissContentViewBottomConstant
}
func update(theme: Theme) {
self.contentView.backgroundColor = theme.headerBackgroundColor
}
func updateContentViewMaxHeight(_ maxHeight: CGFloat) {
self.contentViewHeightConstraint.constant = maxHeight
}
func updateContentViewLayout() {
self.layoutIfNeeded()
}
func setContentView(_ contentView: UIView) {
for subView in self.contentView.subviews {
subView.removeFromSuperview()
}
self.contentView.vc_addSubViewMatchingParent(contentView)
}
func updateDimmingViewAlpha(_ alpha: CGFloat) {
self.dimmingView.alpha = alpha
}
func contentViewWidthFittingSize(_ size: CGSize) -> CGFloat {
let sizingView = SlidingModalContainerView.Sizing.view
if let widthConstraint = SlidingModalContainerView.Sizing.widthConstraint {
widthConstraint.constant = size.width
} else {
let widthConstraint = sizingView.widthAnchor.constraint(equalToConstant: size.width)
widthConstraint.isActive = true
SlidingModalContainerView.Sizing.widthConstraint = widthConstraint
}
if let heightConstraint = SlidingModalContainerView.Sizing.heightConstraint {
heightConstraint.constant = size.height
} else {
let heightConstraint = sizingView.heightAnchor.constraint(equalToConstant: size.width)
heightConstraint.isActive = true
SlidingModalContainerView.Sizing.heightConstraint = heightConstraint
}
sizingView.setNeedsLayout()
sizingView.layoutIfNeeded()
return sizingView.contentViewFrame.width
}
// MARK: - Private
private func setupBackgroundTapGestureRecognizer() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap(_:)))
self.dimmingView.addGestureRecognizer(tapGestureRecognizer)
}
@objc private func handleBackgroundTap(_ gestureRecognizer: UITapGestureRecognizer) {
self.delegate?.slidingModalContainerViewDidTapBackground(self)
}
}

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" 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>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="SlidingModalContainerView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view alpha="0.69999999999999996" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="53q-mu-x6X">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QWp-44-y8i">
<rect key="frame" x="10" y="562" width="394" height="300"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" priority="750" constant="300" id="KJn-4o-kRn"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="394" id="R8B-L1-f8s"/>
<constraint firstAttribute="width" priority="750" constant="394" id="XB0-Pe-HuZ"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="53q-mu-x6X" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="6Vb-zr-n6S"/>
<constraint firstAttribute="trailing" secondItem="53q-mu-x6X" secondAttribute="trailing" id="EZg-k9-QuF"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="QWp-44-y8i" secondAttribute="trailing" constant="10" id="GN0-Hk-Sfk"/>
<constraint firstItem="QWp-44-y8i" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="10" id="LpX-Ga-7HY"/>
<constraint firstAttribute="bottom" secondItem="53q-mu-x6X" secondAttribute="bottom" id="NoA-lJ-53P"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="QWp-44-y8i" secondAttribute="bottom" id="Pvh-aU-qnh"/>
<constraint firstItem="QWp-44-y8i" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="YkU-c0-Cgo"/>
<constraint firstItem="53q-mu-x6X" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="lJ8-Oz-pLZ"/>
<constraint firstItem="QWp-44-y8i" firstAttribute="top" relation="greaterThanOrEqual" secondItem="vUN-kp-3ea" secondAttribute="top" constant="50" id="nnr-Lr-vpY"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<connections>
<outlet property="contentView" destination="QWp-44-y8i" id="mFc-eZ-nx2"/>
<outlet property="contentViewBottomConstraint" destination="Pvh-aU-qnh" id="U4M-ey-LnG"/>
<outlet property="contentViewHeightConstraint" destination="KJn-4o-kRn" id="VUT-sf-ebZ"/>
<outlet property="dimmingView" destination="53q-mu-x6X" id="L4V-r2-NUX"/>
</connections>
</view>
</objects>
</document>

View file

@ -0,0 +1,51 @@
/*
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
/// Empty view controller used to embed a view conforming to `SlidingModalPresentable`.
final class SlidingModalEmptyViewController: UIViewController {
// MARK: - Properties
private var modalView: SlidingModalPresentable.ViewType!
// MARK: - Setup
static func instantiate(with view: SlidingModalPresentable.ViewType) -> SlidingModalEmptyViewController {
let viewController = SlidingModalEmptyViewController()
viewController.modalView = view
return viewController
}
// MARK: - Life cycle
override func loadView() {
self.view = self.modalView
}
}
// MARK: - SlidingModalPresentable
extension SlidingModalEmptyViewController: SlidingModalPresentable {
func allowsDismissOnBackgroundTap() -> Bool {
return self.modalView.allowsDismissOnBackgroundTap()
}
func layoutHeightFittingWidth(_ width: CGFloat) -> CGFloat {
return self.modalView.layoutHeightFittingWidth(width)
}
}

View file

@ -0,0 +1,29 @@
/*
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
/// `SlidingModalPresentable` is a protocol describing a UI element to present modally using `SlidingModalPresenter`.
@objc protocol SlidingModalPresentable {
typealias ViewType = UIView & SlidingModalPresentable
typealias ViewControllerType = UIViewController & SlidingModalPresentable
func allowsDismissOnBackgroundTap() -> Bool
func layoutHeightFittingWidth(_ width: CGFloat) -> CGFloat
}

View file

@ -0,0 +1,135 @@
/*
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
/// `SlidingModalPresentationAnimator` handles the animations for a custom sliding view controller transition.
final class SlidingModalPresentationAnimator: NSObject {
// MARK: - Constants
private enum AnimationDuration {
static let presentation: TimeInterval = 0.2
static let dismissal: TimeInterval = 0.3
}
// MARK: - Properties
private let isPresenting: Bool
// MARK: - Setup
/// Instantiate a SlidingModalPresentationAnimator object.
///
/// - Parameter isPresenting: true to animate presentation or false to animate dismissal
required public init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
// MARK: - Private
// Animate presented view controller presentation
private func animatePresentation(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedViewController = transitionContext.viewController(forKey: .to),
let sourceViewController = transitionContext.viewController(forKey: .from) else {
return
}
guard let presentedViewControllerView = presentedViewController.view else {
return
}
let containerView = transitionContext.containerView
let slidingModalContainerView = SlidingModalContainerView.instantiate()
slidingModalContainerView.alpha = 0
slidingModalContainerView.updateDimmingViewAlpha(0.0)
// Add presented view controller view to slidingModalContainerView content view
slidingModalContainerView.setContentView(presentedViewControllerView)
// Add slidingModalContainerView to container view
containerView.vc_addSubViewMatchingParent(slidingModalContainerView)
containerView.layoutIfNeeded()
// Adapt slidingModalContainerView content view height from presentedViewControllerView height
if let slidingModalPresentable = presentedViewController as? SlidingModalPresentable {
let slidingModalContainerViewContentViewWidth = slidingModalContainerView.contentViewFrame.width
let presentableHeight = slidingModalPresentable.layoutHeightFittingWidth(slidingModalContainerViewContentViewWidth)
slidingModalContainerView.updateContentViewMaxHeight(presentableHeight)
slidingModalContainerView.updateContentViewLayout()
}
// Hide slidingModalContainerView content view
slidingModalContainerView.prepareDismissAnimation()
containerView.layoutIfNeeded()
let animationDuration = self.transitionDuration(using: transitionContext)
slidingModalContainerView.preparePresentAnimation()
slidingModalContainerView.alpha = 1
UIView.animate(withDuration: animationDuration, animations: {
containerView.layoutIfNeeded()
slidingModalContainerView.updateDimmingViewAlpha(1.0)
}, completion: { completed in
transitionContext.completeTransition(completed)
})
}
// Animate presented view controller dismissal
private func animateDismissal(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let slidingModalContainerView = self.slidingModalContainerView(from: transitionContext)
let animationDuration = self.transitionDuration(using: transitionContext)
slidingModalContainerView?.prepareDismissAnimation()
UIView.animate(withDuration: animationDuration, animations: {
containerView.layoutIfNeeded()
slidingModalContainerView?.updateDimmingViewAlpha(0.0)
}, completion: { completed in
transitionContext.completeTransition(completed)
})
}
private func slidingModalContainerView(from transitionContext: UIViewControllerContextTransitioning) -> SlidingModalContainerView? {
let modalContentView = transitionContext.containerView.subviews.first(where: { view -> Bool in
view is SlidingModalContainerView
}) as? SlidingModalContainerView
return modalContentView
}
}
// MARK: - UIViewControllerAnimatedTransitioning
extension SlidingModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.isPresenting ? AnimationDuration.presentation : AnimationDuration.dismissal
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if self.isPresenting {
self.animatePresentation(using: transitionContext)
} else {
self.animateDismissal(using: transitionContext)
}
}
}

View file

@ -0,0 +1,106 @@
/*
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
/// `SlidingModalPresentationController` handles sliding transition presentation life cycle.
final class SlidingModalPresentationController: UIPresentationController {
// MARK: - Properties
private var slidingModalContainerView: SlidingModalContainerView? {
return self.containerView?.subviews.first(where: { (view) -> Bool in
view is SlidingModalContainerView
}) as? SlidingModalContainerView
}
// MARK: - Setup
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
// MARK: - Life cycle
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if let slidingModalPresentable = self.presentedViewController as? SlidingModalPresentable, let slidingModalContainerView = self.slidingModalContainerView {
let slidingModalContainerViewContentViewWidth = slidingModalContainerView.contentViewWidthFittingSize(size)
let presentableHeight = slidingModalPresentable.layoutHeightFittingWidth(slidingModalContainerViewContentViewWidth)
slidingModalContainerView.updateContentViewMaxHeight(presentableHeight)
}
coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) -> Void in
self.slidingModalContainerView?.updateContentViewLayout()
}, completion: { _ in
})
}
override func presentationTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: { [weak self] _ in
// Update status bar appearance of presented view controller (if presenting view controller allowed it, see `modalPresentationCapturesStatusBarAppearance` property)
self?.presentedViewController.setNeedsStatusBarAppearanceUpdate()
}, completion: nil)
}
override func presentationTransitionDidEnd(_ completed: Bool) {
self.slidingModalContainerView?.delegate = self
}
override func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: { [weak self] _ -> Void in
// Update status bar appearance of presenting view controller
self?.presentingViewController.setNeedsStatusBarAppearanceUpdate()
}, completion: nil)
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
self.slidingModalContainerView?.removeFromSuperview()
}
}
}
// MARK: - SlidingModalContainerViewDelegate
extension SlidingModalPresentationController: SlidingModalContainerViewDelegate {
func slidingModalContainerViewDidTapBackground(_ view: SlidingModalContainerView) {
let isDismissOnBackgroundTapAllowed: Bool
if let slidingModalPresentable = self.presentedViewController as? SlidingModalPresentable {
isDismissOnBackgroundTapAllowed = slidingModalPresentable.allowsDismissOnBackgroundTap()
} else {
isDismissOnBackgroundTapAllowed = true
}
if isDismissOnBackgroundTapAllowed {
self.presentedViewController.dismiss(animated: true, completion: nil)
}
}
}

View file

@ -0,0 +1,48 @@
/*
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
/// `SlidingModalPresentationDelegate` handle a custom sliding UIViewController transition.
public class SlidingModalPresentationDelegate: NSObject {
}
// MARK: - UIViewControllerTransitioningDelegate
extension SlidingModalPresentationDelegate: UIViewControllerTransitioningDelegate {
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SlidingModalPresentationAnimator(isPresenting: true)
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SlidingModalPresentationAnimator(isPresenting: false)
}
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let controller = SlidingModalPresentationController(presentedViewController: presented, presenting: presenting)
controller.delegate = self
return controller
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension SlidingModalPresentationDelegate: UIAdaptivePresentationControllerDelegate {
// Do not adapt to size classes
public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}

View file

@ -0,0 +1,72 @@
/*
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
/// `SlidingModalPresenter` allows to present a custom UIViewController or UIView conforming to `SlidingModalPresentable` as a modal with a vertical sliding animation from a UIViewController.
final class SlidingModalPresenter: NSObject {
// MARK: - Constants
private enum TabletContentSize {
static let preferred = CGSize(width: 400.0, height: 400.0)
static let minHeight: CGFloat = 0.0
static let maxHeight: CGFloat = 600.0
}
// MARK: - Properties
// swiftlint:disable weak_delegate
private var transitionDelegate: SlidingModalPresentationDelegate?
// swiftlint:enable weak_delegate
private weak var presentingViewController: UIViewController?
// MARK: - Public
@objc func present(_ viewController: SlidingModalPresentable.ViewControllerType, from presentingViewController: UIViewController, animated: Bool, completion: (() -> Void)?) {
if UIDevice.current.userInterfaceIdiom == .pad {
viewController.modalPresentationStyle = .formSheet
let preferredHeight = viewController.layoutHeightFittingWidth(TabletContentSize.preferred.width).clamped(to: TabletContentSize.minHeight...TabletContentSize.maxHeight)
viewController.preferredContentSize = CGSize(width: TabletContentSize.preferred.width, height: preferredHeight)
} else {
let transitionDelegate = SlidingModalPresentationDelegate()
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitionDelegate
// Presented view controller does not affect the statusbar appearance
viewController.modalPresentationCapturesStatusBarAppearance = false
self.transitionDelegate = transitionDelegate
}
presentingViewController.present(viewController, animated: animated, completion: completion)
self.presentingViewController = presentingViewController
}
@objc func presentView(_ view: SlidingModalPresentable.ViewType, from viewControllerPresenter: UIViewController, animated: Bool, completion: (() -> Void)?) {
let viewController = SlidingModalEmptyViewController.instantiate(with: view)
self.present(viewController, from: viewControllerPresenter, animated: animated, completion: completion)
}
@objc func dismiss(animated: Bool, completion: (() -> Void)?) {
self.presentingViewController?.dismiss(animated: animated, completion: completion)
}
}

View file

@ -41,14 +41,17 @@ final class NavigationRouter: NSObject, NavigationRouterType {
// MARK: - Public
func present(_ module: Presentable, animated: Bool = true) {
NSLog("[NavigationRouter] Present \(module)")
navigationController.present(module.toPresentable(), animated: animated, completion: nil)
}
func dismissModule(animated: Bool = true, completion: (() -> Void)? = nil) {
NSLog("[NavigationRouter] Dismiss presented module")
navigationController.dismiss(animated: animated, completion: completion)
}
func setRootModule(_ module: Presentable, hideNavigationBar: Bool = false, animated: Bool = false, popCompletion: (() -> Void)? = nil) {
NSLog("[NavigationRouter] Set root module \(module)")
let controller = module.toPresentable()
@ -58,7 +61,9 @@ final class NavigationRouter: NSObject, NavigationRouterType {
}
// Call all completions so all coordinators can be deallocated
completions.forEach { $0.value() }
for presentable in completions.keys {
runCompletion(for: presentable)
}
if let popCompletion = popCompletion {
completions[controller] = popCompletion
@ -69,18 +74,23 @@ final class NavigationRouter: NSObject, NavigationRouterType {
}
func popToRootModule(animated: Bool) {
NSLog("[NavigationRouter] Pop to root module")
if let controllers = navigationController.popToRootViewController(animated: animated) {
controllers.forEach { runCompletion(for: $0) }
}
}
func popToModule(_ module: Presentable, animated: Bool) {
NSLog("[NavigationRouter] Pop to module \(module)")
if let controllers = navigationController.popToViewController(module.toPresentable(), animated: animated) {
controllers.forEach { runCompletion(for: $0) }
}
}
func push(_ module: Presentable, animated: Bool = true, popCompletion: (() -> Void)? = nil) {
NSLog("[NavigationRouter] Push module \(module)")
let controller = module.toPresentable()
@ -97,6 +107,8 @@ final class NavigationRouter: NSObject, NavigationRouterType {
}
func popModule(animated: Bool = true) {
NSLog("[NavigationRouter] Pop module")
if let controller = navigationController.popViewController(animated: animated) {
runCompletion(for: controller)
}
@ -111,7 +123,9 @@ final class NavigationRouter: NSObject, NavigationRouterType {
// MARK: - Private
private func runCompletion(for controller: UIViewController) {
guard let completion = completions[controller] else { return }
guard let completion = completions[controller] else {
return
}
completion()
completions.removeValue(forKey: controller)
}
@ -128,6 +142,8 @@ extension NavigationRouter: UINavigationControllerDelegate {
return
}
NSLog("[NavigationRouter] Poppped module: \(poppedViewController)")
runCompletion(for: poppedViewController)
}
}

View file

@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.10.2</string>
<string>0.10.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.10.2</string>
<string>0.10.4</string>
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<key>ITSEncryptionExportComplianceCode</key>

View file

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>0.10.2</string>
<string>0.10.4</string>
<key>CFBundleVersion</key>
<string>0.10.2</string>
<string>0.10.4</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View file

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>0.10.2</string>
<string>0.10.4</string>
<key>CFBundleVersion</key>
<string>0.10.2</string>
<string>0.10.4</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>