Merge commit 'ace42be63764c1f1aec82d6e3448ca8980adc784' into feature/3746_merge_element_1.9.10

# Conflicts:
#	Config/AppConfiguration.swift
#	Config/AppVersion.xcconfig
#	Podfile.lock
#	Riot/Modules/Application/AppCoordinator.swift
#	Riot/Modules/Common/Avatar/AvatarView.swift
#	Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m
#	Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m
#	Riot/Modules/Settings/Security/SecurityViewController.m
#	Riot/Modules/Settings/SettingsViewController.m
#	Riot/Modules/TabBar/TabBarCoordinator.swift
#	Riot/target.yml
#	fastlane/Fastfile
#	project.yml
This commit is contained in:
Frank Rotermund
2022-11-02 14:05:36 +01:00
467 changed files with 18708 additions and 2131 deletions
@@ -26,7 +26,7 @@ struct RoomAvatarViewData: AvatarViewDataProtocol {
return roomId
}
var fallbackImage: AvatarFallbackImage? {
return .matrixItem(matrixItemId, displayName)
var fallbackImages: [AvatarFallbackImage]? {
[.matrixItem(matrixItemId, displayName)]
}
}
@@ -27,7 +27,7 @@
bundle:[NSBundle bundleForClass:[DisabledRoomInputToolbarView class]]];
}
+ (instancetype)roomInputToolbarView
+ (MXKRoomInputToolbarView *)instantiateRoomInputToolbarView
{
if ([[self class] nib])
{
@@ -139,7 +139,7 @@ class RoomInputToolbarTextView: UITextView {
}
private func updateUI() {
var height = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)).height
var height = contentSize.height
height = minHeight > 0 ? max(height, minHeight) : height
height = maxHeight > 0 ? min(height, maxHeight) : height
@@ -33,6 +33,17 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode)
};
@protocol RoomInputToolbarViewProtocol
@property (nonatomic, strong) NSString *eventSenderDisplayName;
@property (nonatomic, assign) RoomInputToolbarViewSendMode sendMode;
@property (nonatomic, assign) BOOL isEncryptionEnabled;
- (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView;
- (CGFloat)toolbarHeight;
@end
@protocol RoomInputToolbarViewDelegate <MXKRoomInputToolbarViewDelegate>
/**
@@ -40,7 +51,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode)
@param toolbarView the room input toolbar view
*/
- (void)roomInputToolbarViewDidTapCancel:(RoomInputToolbarView*)toolbarView;
- (void)roomInputToolbarViewDidTapCancel:(MXKRoomInputToolbarView<RoomInputToolbarViewProtocol>*)toolbarView;
/**
Inform the delegate that the text message has changed.
@@ -70,7 +81,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode)
`RoomInputToolbarView` instance is a view used to handle all kinds of available inputs
for a room (message composer, attachments selection...).
*/
@interface RoomInputToolbarView : MXKRoomInputToolbarView
@interface RoomInputToolbarView : MXKRoomInputToolbarView<RoomInputToolbarViewProtocol>
/**
The delegate notified when inputs are ready.
@@ -19,7 +19,6 @@
#import "ThemeService.h"
#import "GeneratedInterface-Swift.h"
#import "GBDeviceInfo_iOS.h"
static const CGFloat kContextBarHeight = 24;
static const CGFloat kActionMenuAttachButtonSpringVelocity = 7;
@@ -59,7 +58,7 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
@implementation RoomInputToolbarView
@dynamic delegate;
+ (instancetype)roomInputToolbarView
+ (MXKRoomInputToolbarView *)instantiateRoomInputToolbarView
{
UINib *nib = [UINib nibWithNibName:NSStringFromClass([RoomInputToolbarView class]) bundle:nil];
return [nib instantiateWithOwner:nil options:nil].firstObject;
@@ -85,25 +84,6 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
self.textView.inputAccessoryView = inputAccessoryViewForKeyboard;
}
- (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView
{
if (voiceMessageToolbarView) {
_voiceMessageToolbarView = voiceMessageToolbarView;
self.voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.voiceMessageToolbarView];
[NSLayoutConstraint activateConstraints:@[[self.mainToolbarView.topAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.topAnchor],
[self.mainToolbarView.leftAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.leftAnchor],
[self.mainToolbarView.bottomAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.bottomAnchor],
[self.mainToolbarView.rightAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.rightAnchor]]];
}
else
{
[self.voiceMessageToolbarView removeFromSuperview];
_voiceMessageToolbarView = nil;
}
}
#pragma mark - Override MXKView
-(void)customizeViewRendering
@@ -300,69 +280,6 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
}
}
- (void)updatePlaceholder
{
// Consider the default placeholder
NSString *placeholder;
// Check the device screen size before using large placeholder
BOOL shouldDisplayLargePlaceholder = [GBDeviceInfo deviceInfo].family == GBDeviceFamilyiPad || [GBDeviceInfo deviceInfo].displayInfo.display >= GBDeviceDisplay5p8Inch;
if (!shouldDisplayLargePlaceholder)
{
switch (_sendMode)
{
case RoomInputToolbarViewSendModeReply:
placeholder = [VectorL10n roomMessageReplyToShortPlaceholder];
break;
case RoomInputToolbarViewSendModeCreateDM:
placeholder = [VectorL10n roomFirstMessagePlaceholder];
break;
default:
placeholder = [VectorL10n roomMessageShortPlaceholder];
break;
}
}
else
{
if (_isEncryptionEnabled)
{
switch (_sendMode)
{
case RoomInputToolbarViewSendModeReply:
placeholder = [VectorL10n encryptedRoomMessageReplyToPlaceholder];
break;
default:
placeholder = [VectorL10n encryptedRoomMessagePlaceholder];
break;
}
}
else
{
switch (_sendMode)
{
case RoomInputToolbarViewSendModeReply:
placeholder = [VectorL10n roomMessageReplyToPlaceholder];
break;
case RoomInputToolbarViewSendModeCreateDM:
placeholder = [VectorL10n roomFirstMessagePlaceholder];
break;
default:
placeholder = [VectorL10n roomMessagePlaceholder];
break;
}
}
}
self.placeholder = placeholder;
}
- (void)setPlaceholder:(NSString *)inPlaceholder
{
[super setPlaceholder:inPlaceholder];
@@ -543,4 +460,28 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
}];
}
#pragma mark - RoomInputToolbarViewProtocol
- (CGFloat)toolbarHeight {
return self.mainToolbarHeightConstraint.constant;
}
- (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView
{
if (voiceMessageToolbarView) {
_voiceMessageToolbarView = voiceMessageToolbarView;
self.voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.voiceMessageToolbarView];
[NSLayoutConstraint activateConstraints:@[[self.mainToolbarView.topAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.topAnchor],
[self.mainToolbarView.leftAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.leftAnchor],
[self.mainToolbarView.bottomAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.bottomAnchor],
[self.mainToolbarView.rightAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.rightAnchor]]];
}
else
{
[self.voiceMessageToolbarView removeFromSuperview];
_voiceMessageToolbarView = nil;
}
}
@end
@@ -16,6 +16,7 @@
import Foundation
import UIKit
import GBDeviceInfo
extension RoomInputToolbarView {
open override func sendCurrentMessage() {
@@ -28,15 +29,66 @@ extension RoomInputToolbarView {
self.becomeFirstResponder()
temp.removeFromSuperview()
}
// Send message if any.
if let messageToSend = self.attributedTextMessage, messageToSend.length > 0 {
self.delegate.roomInputToolbarView(self, sendAttributedTextMessage: messageToSend)
}
// Reset message, disable view animation during the update to prevent placeholder distorsion.
UIView.setAnimationsEnabled(false)
self.attributedTextMessage = nil
UIView.setAnimationsEnabled(true)
}
}
@objc extension RoomInputToolbarView {
func updatePlaceholder() {
updatePlaceholderText()
}
}
extension RoomInputToolbarViewProtocol where Self: MXKRoomInputToolbarView {
func updatePlaceholderText() {
// Consider the default placeholder
let placeholder: String
// Check the device screen size before using large placeholder
let shouldDisplayLargePlaceholder = GBDeviceInfo.deviceInfo().family == .familyiPad || GBDeviceInfo.deviceInfo().displayInfo.display.rawValue >= GBDeviceDisplay.display5p8Inch.rawValue
if !shouldDisplayLargePlaceholder {
switch sendMode {
case .reply:
placeholder = VectorL10n.roomMessageReplyToShortPlaceholder
case .createDM:
placeholder = VectorL10n.roomFirstMessagePlaceholder
default:
placeholder = VectorL10n.roomMessageShortPlaceholder
}
} else {
if isEncryptionEnabled {
switch sendMode {
case .reply:
placeholder = VectorL10n.encryptedRoomMessageReplyToPlaceholder
default:
placeholder = VectorL10n.encryptedRoomMessagePlaceholder
}
} else {
switch sendMode {
case .reply:
placeholder = VectorL10n.roomMessageReplyToPlaceholder
case .createDM:
placeholder = VectorL10n.roomFirstMessagePlaceholder
default:
placeholder = VectorL10n.roomMessagePlaceholder
}
}
}
self.placeholder = placeholder
}
}
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -27,7 +27,7 @@
<action selector="onTouchUpInside:" destination="iN0-l3-epB" eventType="touchUpInside" id="WbU-WH-gwL"/>
</connections>
</button>
<scrollView hidden="YES" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ESv-9w-KJF" customClass="RoomActionsBar" customModule="Riot" customModuleProvider="target">
<scrollView hidden="YES" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ESv-9w-KJF" customClass="RoomActionsBar" customModule="Element" customModuleProvider="target">
<rect key="frame" x="60" y="8" width="540" height="38"/>
<constraints>
<constraint firstAttribute="height" constant="38" id="i6C-gL-ADZ"/>
@@ -41,7 +41,7 @@
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="input_text_background" translatesAutoresizingMaskIntoConstraints="NO" id="uH7-Q7-hpZ">
<rect key="frame" x="0.0" y="0.0" width="484" height="36"/>
</imageView>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jXI-9E-Bgl">
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jXI-9E-Bgl" userLabel="Input Context View">
<rect key="frame" x="0.0" y="0.0" width="484" height="32"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="input_edit_icon" translatesAutoresizingMaskIntoConstraints="NO" id="PZ4-0Y-TmL">
@@ -69,7 +69,7 @@
<constraints>
<constraint firstAttribute="bottom" secondItem="dVr-ZM-kkX" secondAttribute="bottom" constant="4" id="58M-yd-hhK"/>
<constraint firstItem="PZ4-0Y-TmL" firstAttribute="centerY" secondItem="dVr-ZM-kkX" secondAttribute="centerY" id="Bu1-sl-qDk"/>
<constraint firstAttribute="height" constant="32" id="KNn-ng-NHK"/>
<constraint firstAttribute="height" priority="750" constant="32" id="KNn-ng-NHK"/>
<constraint firstItem="dVr-ZM-kkX" firstAttribute="leading" secondItem="PZ4-0Y-TmL" secondAttribute="trailing" constant="4" id="RbN-mc-y2P"/>
<constraint firstItem="48y-kn-7b5" firstAttribute="leading" secondItem="dVr-ZM-kkX" secondAttribute="trailing" constant="4" id="bmi-rg-TNM"/>
<constraint firstItem="PZ4-0Y-TmL" firstAttribute="leading" secondItem="jXI-9E-Bgl" secondAttribute="leading" constant="8" id="mp0-tl-IIe"/>
@@ -77,7 +77,7 @@
<constraint firstItem="48y-kn-7b5" firstAttribute="centerY" secondItem="dVr-ZM-kkX" secondAttribute="centerY" id="z5v-Vy-6tc"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wgb-ON-N29" customClass="RoomInputToolbarTextView" customModule="Riot" customModuleProvider="target">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wgb-ON-N29" customClass="RoomInputToolbarTextView" customModule="Element" customModuleProvider="target">
<rect key="frame" x="5" y="33" width="474" height="4"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="GrowingTextView"/>
@@ -0,0 +1,257 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import Reusable
import WysiwygComposer
import SwiftUI
import Combine
import UIKit
import CoreGraphics
@objc protocol HtmlRoomInputToolbarViewProtocol: RoomInputToolbarViewProtocol {
@objc var htmlContent: String { get set }
}
// The toolbar for editing with rich text
class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInputToolbarViewProtocol {
// MARK: - Properties
// MARK: Private
private var voiceMessageToolbarView: VoiceMessageToolbarView?
private var cancellables = Set<AnyCancellable>()
private var heightConstraint: NSLayoutConstraint!
private var hostingViewController: VectorHostingController!
private var wysiwygViewModel = WysiwygComposerViewModel(textColor: ThemeService.shared().theme.colors.primaryContent)
private var viewModel: ComposerViewModelProtocol = ComposerViewModel(initialViewState: ComposerViewState())
// MARK: Public
override var placeholder: String! {
get {
viewModel.placeholder
}
set {
viewModel.placeholder = newValue
}
}
// MARK: - Setup
override class func instantiate() -> MXKRoomInputToolbarView! {
return loadFromNib()
}
private weak var toolbarViewDelegate: RoomInputToolbarViewDelegate? {
return (delegate as? RoomInputToolbarViewDelegate) ?? nil
}
override func awakeFromNib() {
super.awakeFromNib()
viewModel.callback = { [weak self] result in
self?.handleViewModelResult(result)
}
inputAccessoryViewForKeyboard = UIView(frame: .zero)
let composer = Composer(viewModel: viewModel.context,
wysiwygViewModel: wysiwygViewModel,
sendMessageAction: { [weak self] content in
guard let self = self else { return }
self.sendWysiwygMessage(content: content)
}, showSendMediaActions: { [weak self] in
guard let self = self else { return }
self.showSendMediaActions()
}).introspectTextView { [weak self] textView in
guard let self = self else { return }
textView.inputAccessoryView = self.inputAccessoryViewForKeyboard
}
hostingViewController = VectorHostingController(rootView: composer)
hostingViewController.publishHeightChanges = true
let height = hostingViewController.sizeThatFits(in: CGSize(width: self.frame.width, height: UIView.layoutFittingExpandedSize.height)).height
let subView: UIView = hostingViewController.view
self.addSubview(subView)
self.translatesAutoresizingMaskIntoConstraints = false
subView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint = subView.heightAnchor.constraint(equalToConstant: height)
NSLayoutConstraint.activate([
heightConstraint,
subView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
subView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
subView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
cancellables = [
hostingViewController.heightPublisher
.removeDuplicates()
.sink(receiveValue: { [weak self] idealHeight in
guard let self = self else { return }
self.updateToolbarHeight(wysiwygHeight: idealHeight)
}),
// Required to update the view constraints after minimise/maximise is tapped
wysiwygViewModel.$idealHeight
.removeDuplicates()
.sink { [weak hostingViewController] _ in
hostingViewController?.view.setNeedsLayout()
}
]
update(theme: ThemeService.shared().theme)
registerThemeServiceDidChangeThemeNotification()
}
override func customizeRendering() {
super.customizeRendering()
self.backgroundColor = .clear
}
// MARK: - Private
private func updateToolbarHeight(wysiwygHeight: CGFloat) {
self.heightConstraint.constant = wysiwygHeight
toolbarViewDelegate?.roomInputToolbarView?(self, heightDidChanged: wysiwygHeight, completion: nil)
}
private func sendWysiwygMessage(content: WysiwygComposerContent) {
delegate?.roomInputToolbarView?(self, sendFormattedTextMessage: content.html, withRawText: content.plainText)
}
private func showSendMediaActions() {
delegate?.roomInputToolbarViewShowSendMediaActions?(self)
}
private func handleViewModelResult(_ result: ComposerViewModelResult) {
switch result {
case .cancel:
self.toolbarViewDelegate?.roomInputToolbarViewDidTapCancel(self)
case let .contentDidChange(isEmpty):
setVoiceMessageToolbarIsHidden(!isEmpty)
}
}
private func setVoiceMessageToolbarIsHidden(_ isHidden: Bool) {
guard let voiceMessageToolbarView = voiceMessageToolbarView else { return }
UIView.transition(
with: voiceMessageToolbarView, duration: 0.15,
options: .transitionCrossDissolve,
animations: {
voiceMessageToolbarView.isHidden = isHidden
}
)
}
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 update(theme: Theme) {
hostingViewController.view.backgroundColor = theme.colors.background
wysiwygViewModel.textColor = theme.colors.primaryContent
}
// MARK: - HtmlRoomInputToolbarViewProtocol
var isEncryptionEnabled = false {
didSet {
updatePlaceholderText()
}
}
/// The current html content of the composer
var htmlContent: String {
get {
wysiwygViewModel.content.html
}
set {
wysiwygViewModel.setHtmlContent(newValue)
}
}
/// The display name to show when in edit/reply
var eventSenderDisplayName: String! {
get {
viewModel.eventSenderDisplayName
}
set {
viewModel.eventSenderDisplayName = newValue
}
}
/// Whether the composer is in send, reply or edit mode.
var sendMode: RoomInputToolbarViewSendMode {
get {
viewModel.sendMode.legacySendMode
}
set {
viewModel.sendMode = ComposerSendMode(from: newValue)
updatePlaceholderText()
}
}
/// Add the voice message toolbar to the composer
/// - Parameter voiceMessageToolbarView: the voice message toolbar UIView
func setVoiceMessageToolbarView(_ voiceMessageToolbarView: UIView!) {
if let voiceMessageToolbarView = voiceMessageToolbarView as? VoiceMessageToolbarView {
self.voiceMessageToolbarView = voiceMessageToolbarView
voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.deactivate(voiceMessageToolbarView.containersTopConstraints)
addSubview(voiceMessageToolbarView)
NSLayoutConstraint.activate(
[
hostingViewController.view.topAnchor.constraint(equalTo: voiceMessageToolbarView.topAnchor),
hostingViewController.view.leftAnchor.constraint(equalTo: voiceMessageToolbarView.leftAnchor),
hostingViewController.view.bottomAnchor.constraint(equalTo: voiceMessageToolbarView.bottomAnchor, constant: 4),
hostingViewController.view.rightAnchor.constraint(equalTo: voiceMessageToolbarView.rightAnchor)
]
)
} else {
self.voiceMessageToolbarView?.removeFromSuperview()
self.voiceMessageToolbarView = nil
}
}
func toolbarHeight() -> CGFloat {
return heightConstraint.constant
}
}
// MARK: - LegacySendModeAdapter
fileprivate extension ComposerSendMode {
init(from sendMode: RoomInputToolbarViewSendMode) {
switch sendMode {
case .reply: self = .reply
case .edit: self = .edit
case .createDM: self = .createDM
default: self = .send
}
}
var legacySendMode: RoomInputToolbarViewSendMode {
switch self {
case .createDM: return .createDM
case .reply: return .reply
case .edit: return .edit
case .send: return .send
}
}
}
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<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="WysiwygInputToolbarView" customModule="Element" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="600" height="80"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="139" y="101"/>
</view>
</objects>
</document>