Improve accessibility
This commit is contained in:
parent
c1595a6e92
commit
6de6c8cf5d
|
@ -31,6 +31,14 @@ final class NewConversationButtonSet : UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setUpViewHierarchy() {
|
private func setUpViewHierarchy() {
|
||||||
|
mainButton.accessibilityLabel = "Toggle conversation options button"
|
||||||
|
mainButton.isAccessibilityElement = true
|
||||||
|
createNewPrivateChatButton.accessibilityLabel = "Start new one-on-one conversation button"
|
||||||
|
createNewPrivateChatButton.isAccessibilityElement = true
|
||||||
|
createNewClosedGroupButton.accessibilityLabel = "Start new closed group button"
|
||||||
|
createNewClosedGroupButton.isAccessibilityElement = true
|
||||||
|
joinOpenGroupButton.accessibilityLabel = "Join open group button"
|
||||||
|
joinOpenGroupButton.isAccessibilityElement = true
|
||||||
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
|
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
|
||||||
addSubview(joinOpenGroupButton)
|
addSubview(joinOpenGroupButton)
|
||||||
horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset)
|
horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset)
|
||||||
|
|
|
@ -127,6 +127,8 @@ const CGFloat kMaxTextViewHeight = 120;
|
||||||
self.inputTextView.backgroundColor = LKColors.composeViewTextFieldBackground;
|
self.inputTextView.backgroundColor = LKColors.composeViewTextFieldBackground;
|
||||||
[self.inputTextView setContentHuggingLow];
|
[self.inputTextView setContentHuggingLow];
|
||||||
[self.inputTextView setCompressionResistanceLow];
|
[self.inputTextView setCompressionResistanceLow];
|
||||||
|
self.inputTextView.accessibilityLabel = @"Input text view";
|
||||||
|
self.inputTextView.isAccessibilityElement = YES;
|
||||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _inputTextView);
|
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _inputTextView);
|
||||||
|
|
||||||
_textViewHeightConstraint = [self.inputTextView autoSetDimension:ALDimensionHeight toSize:kMinTextViewHeight];
|
_textViewHeightConstraint = [self.inputTextView autoSetDimension:ALDimensionHeight toSize:kMinTextViewHeight];
|
||||||
|
@ -147,11 +149,15 @@ const CGFloat kMaxTextViewHeight = 120;
|
||||||
[self.sendButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
|
[self.sendButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
|
||||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _sendButton);
|
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _sendButton);
|
||||||
[self.sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
[self.sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
self.sendButton.accessibilityLabel = @"Send button";
|
||||||
|
self.sendButton.isAccessibilityElement = YES;
|
||||||
|
|
||||||
_voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
_voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
UIImage *voiceMemoIcon = [[UIImage imageNamed:@"Microphone"] asTintedImageWithColor:LKColors.text];
|
UIImage *voiceMemoIcon = [[UIImage imageNamed:@"Microphone"] asTintedImageWithColor:LKColors.text];
|
||||||
[self.voiceMemoButton setImage:voiceMemoIcon forState:UIControlStateNormal];
|
[self.voiceMemoButton setImage:voiceMemoIcon forState:UIControlStateNormal];
|
||||||
[self.voiceMemoButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
|
[self.voiceMemoButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
|
||||||
|
self.voiceMemoButton.accessibilityLabel = @"Voice message button";
|
||||||
|
self.voiceMemoButton.isAccessibilityElement = YES;
|
||||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _voiceMemoButton);
|
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _voiceMemoButton);
|
||||||
|
|
||||||
// We want to be permissive about the voice message gesture, so we hang
|
// We want to be permissive about the voice message gesture, so we hang
|
||||||
|
|
|
@ -599,6 +599,8 @@ typedef enum : NSUInteger {
|
||||||
self.collectionView.backgroundColor = UIColor.clearColor;
|
self.collectionView.backgroundColor = UIColor.clearColor;
|
||||||
UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Gear"] style:UIBarButtonItemStylePlain target:self action:@selector(showConversationSettings)];
|
UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Gear"] style:UIBarButtonItemStylePlain target:self action:@selector(showConversationSettings)];
|
||||||
settingsButton.tintColor = LKColors.text;
|
settingsButton.tintColor = LKColors.text;
|
||||||
|
settingsButton.accessibilityLabel = @"Conversation settings button";
|
||||||
|
settingsButton.isAccessibilityElement = YES;
|
||||||
self.navigationItem.rightBarButtonItem = settingsButton;
|
self.navigationItem.rightBarButtonItem = settingsButton;
|
||||||
|
|
||||||
if (self.thread.isGroupThread) {
|
if (self.thread.isGroupThread) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ final class DisplayNameVC : BaseVC {
|
||||||
private lazy var displayNameTextField: TextField = {
|
private lazy var displayNameTextField: TextField = {
|
||||||
let result = TextField(placeholder: NSLocalizedString("vc_display_name_text_field_hint", comment: ""))
|
let result = TextField(placeholder: NSLocalizedString("vc_display_name_text_field_hint", comment: ""))
|
||||||
result.layer.borderColor = Colors.text.cgColor
|
result.layer.borderColor = Colors.text.cgColor
|
||||||
|
result.accessibilityLabel = "Display name text field"
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -306,6 +306,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
private func updateNavigationBarButtons() {
|
private func updateNavigationBarButtons() {
|
||||||
let profilePictureSize = Values.verySmallProfilePictureSize
|
let profilePictureSize = Values.verySmallProfilePictureSize
|
||||||
let profilePictureView = ProfilePictureView()
|
let profilePictureView = ProfilePictureView()
|
||||||
|
profilePictureView.accessibilityLabel = "Settings button"
|
||||||
profilePictureView.size = profilePictureSize
|
profilePictureView.size = profilePictureSize
|
||||||
profilePictureView.hexEncodedPublicKey = getUserHexEncodedPublicKey()
|
profilePictureView.hexEncodedPublicKey = getUserHexEncodedPublicKey()
|
||||||
profilePictureView.update()
|
profilePictureView.update()
|
||||||
|
@ -314,24 +315,33 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
|
||||||
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
|
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
|
||||||
let profilePictureViewContainer = UIView()
|
let profilePictureViewContainer = UIView()
|
||||||
|
profilePictureViewContainer.accessibilityLabel = "Settings button"
|
||||||
profilePictureViewContainer.addSubview(profilePictureView)
|
profilePictureViewContainer.addSubview(profilePictureView)
|
||||||
profilePictureView.pin(.leading, to: .leading, of: profilePictureViewContainer, withInset: 4)
|
profilePictureView.pin(.leading, to: .leading, of: profilePictureViewContainer, withInset: 4)
|
||||||
profilePictureView.pin(.top, to: .top, of: profilePictureViewContainer)
|
profilePictureView.pin(.top, to: .top, of: profilePictureViewContainer)
|
||||||
profilePictureView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
|
profilePictureView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
|
||||||
profilePictureView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
|
profilePictureView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
|
let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
|
||||||
|
leftBarButtonItem.accessibilityLabel = "Settings button"
|
||||||
|
leftBarButtonItem.isAccessibilityElement = true
|
||||||
|
navigationItem.leftBarButtonItem = leftBarButtonItem
|
||||||
let pathStatusViewContainer = UIView()
|
let pathStatusViewContainer = UIView()
|
||||||
|
pathStatusViewContainer.accessibilityLabel = "Current onion routing path button"
|
||||||
let pathStatusViewContainerSize = Values.verySmallProfilePictureSize // Match the profile picture view
|
let pathStatusViewContainerSize = Values.verySmallProfilePictureSize // Match the profile picture view
|
||||||
pathStatusViewContainer.set(.width, to: pathStatusViewContainerSize)
|
pathStatusViewContainer.set(.width, to: pathStatusViewContainerSize)
|
||||||
pathStatusViewContainer.set(.height, to: pathStatusViewContainerSize)
|
pathStatusViewContainer.set(.height, to: pathStatusViewContainerSize)
|
||||||
let pathStatusView = PathStatusView()
|
let pathStatusView = PathStatusView()
|
||||||
|
pathStatusView.accessibilityLabel = "Current onion routing path button"
|
||||||
pathStatusView.set(.width, to: Values.pathStatusViewSize)
|
pathStatusView.set(.width, to: Values.pathStatusViewSize)
|
||||||
pathStatusView.set(.height, to: Values.pathStatusViewSize)
|
pathStatusView.set(.height, to: Values.pathStatusViewSize)
|
||||||
pathStatusViewContainer.addSubview(pathStatusView)
|
pathStatusViewContainer.addSubview(pathStatusView)
|
||||||
pathStatusView.center(.horizontal, in: pathStatusViewContainer)
|
pathStatusView.center(.horizontal, in: pathStatusViewContainer)
|
||||||
pathStatusView.center(.vertical, in: pathStatusViewContainer)
|
pathStatusView.center(.vertical, in: pathStatusViewContainer)
|
||||||
pathStatusViewContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showPath)))
|
pathStatusViewContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showPath)))
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: pathStatusViewContainer)
|
let rightBarButtonItem = UIBarButtonItem(customView: pathStatusViewContainer)
|
||||||
|
rightBarButtonItem.accessibilityLabel = "Current onion routing path button"
|
||||||
|
rightBarButtonItem.isAccessibilityElement = true
|
||||||
|
navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override internal func handleAppModeChangedNotification(_ notification: Notification) {
|
@objc override internal func handleAppModeChangedNotification(_ notification: Notification) {
|
||||||
|
|
|
@ -11,8 +11,19 @@ final class PNModeVC : BaseVC, OptionViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Components
|
// MARK: Components
|
||||||
private lazy var apnsOptionView = OptionView(title: "Fast Mode", explanation: "You’ll be notified of new messages reliably and immediately using Apple’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Apple.", delegate: self, isRecommended: true)
|
private lazy var apnsOptionView: OptionView = {
|
||||||
private lazy var backgroundPollingOptionView = OptionView(title: "Slow Mode", explanation: "Session will occasionally check for new messages in the background. Full metadata protection is guaranteed, but message notifications will be unreliable.", delegate: self)
|
let explanation = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Apple."
|
||||||
|
let result = OptionView(title: "Fast Mode", explanation: explanation, delegate: self, isRecommended: true)
|
||||||
|
result.accessibilityLabel = "Fast mode option"
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var backgroundPollingOptionView: OptionView = {
|
||||||
|
let explanation = "Session will occasionally check for new messages in the background. Full metadata protection is guaranteed, but message notifications will be unreliable."
|
||||||
|
let result = OptionView(title: "Slow Mode", explanation: explanation, delegate: self)
|
||||||
|
result.accessibilityLabel = "Slow mode option"
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: Lifecycle
|
// MARK: Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
|
@ -159,6 +159,8 @@ private final class ViewMyQRCodeVC : UIViewController {
|
||||||
qrCodeImageView.set(.width, to: isIPhone5OrSmaller ? 180 : 240)
|
qrCodeImageView.set(.width, to: isIPhone5OrSmaller ? 180 : 240)
|
||||||
// Set up QR code image view container
|
// Set up QR code image view container
|
||||||
let qrCodeImageViewContainer = UIView()
|
let qrCodeImageViewContainer = UIView()
|
||||||
|
qrCodeImageViewContainer.accessibilityLabel = "Your QR code"
|
||||||
|
qrCodeImageViewContainer.isAccessibilityElement = true
|
||||||
qrCodeImageViewContainer.addSubview(qrCodeImageView)
|
qrCodeImageViewContainer.addSubview(qrCodeImageView)
|
||||||
qrCodeImageView.center(.horizontal, in: qrCodeImageViewContainer)
|
qrCodeImageView.center(.horizontal, in: qrCodeImageViewContainer)
|
||||||
qrCodeImageView.pin(.top, to: .top, of: qrCodeImageViewContainer)
|
qrCodeImageView.pin(.top, to: .top, of: qrCodeImageViewContainer)
|
||||||
|
|
|
@ -12,6 +12,7 @@ final class RegisterVC : BaseVC {
|
||||||
result.font = Fonts.spaceMono(ofSize: isIPhone5OrSmaller ? Values.mediumFontSize : 20)
|
result.font = Fonts.spaceMono(ofSize: isIPhone5OrSmaller ? Values.mediumFontSize : 20)
|
||||||
result.numberOfLines = 0
|
result.numberOfLines = 0
|
||||||
result.lineBreakMode = .byCharWrapping
|
result.lineBreakMode = .byCharWrapping
|
||||||
|
result.accessibilityLabel = "Session ID label"
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ final class RestoreVC : BaseVC {
|
||||||
private lazy var mnemonicTextView: TextView = {
|
private lazy var mnemonicTextView: TextView = {
|
||||||
let result = TextView(placeholder: NSLocalizedString("vc_restore_seed_text_field_hint", comment: ""))
|
let result = TextView(placeholder: NSLocalizedString("vc_restore_seed_text_field_hint", comment: ""))
|
||||||
result.layer.borderColor = Colors.text.cgColor
|
result.layer.borderColor = Colors.text.cgColor
|
||||||
|
result.accessibilityLabel = "Recovery phrase text view"
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
||||||
result.size = size
|
result.size = size
|
||||||
result.set(.width, to: size)
|
result.set(.width, to: size)
|
||||||
result.set(.height, to: size)
|
result.set(.height, to: size)
|
||||||
|
result.accessibilityLabel = "Edit profile picture button"
|
||||||
|
result.isAccessibilityElement = true
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
||||||
private lazy var displayNameTextField: TextField = {
|
private lazy var displayNameTextField: TextField = {
|
||||||
let result = TextField(placeholder: NSLocalizedString("vc_settings_display_name_text_field_hint", comment: ""), usesDefaultHeight: false)
|
let result = TextField(placeholder: NSLocalizedString("vc_settings_display_name_text_field_hint", comment: ""), usesDefaultHeight: false)
|
||||||
result.textAlignment = .center
|
result.textAlignment = .center
|
||||||
|
result.accessibilityLabel = "Edit display name text field"
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -69,6 +72,8 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
||||||
displayNameLabel.text = OWSProfileManager.shared().profileNameForRecipient(withID: getUserHexEncodedPublicKey())
|
displayNameLabel.text = OWSProfileManager.shared().profileNameForRecipient(withID: getUserHexEncodedPublicKey())
|
||||||
// Set up display name container
|
// Set up display name container
|
||||||
let displayNameContainer = UIView()
|
let displayNameContainer = UIView()
|
||||||
|
displayNameContainer.accessibilityLabel = "Edit display name text field"
|
||||||
|
displayNameContainer.isAccessibilityElement = true
|
||||||
displayNameContainer.addSubview(displayNameLabel)
|
displayNameContainer.addSubview(displayNameLabel)
|
||||||
displayNameLabel.pin(to: displayNameContainer)
|
displayNameLabel.pin(to: displayNameContainer)
|
||||||
displayNameContainer.addSubview(displayNameTextField)
|
displayNameContainer.addSubview(displayNameTextField)
|
||||||
|
@ -236,13 +241,19 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
||||||
if isEditingDisplayName {
|
if isEditingDisplayName {
|
||||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCancelDisplayNameEditingButtonTapped))
|
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCancelDisplayNameEditingButtonTapped))
|
||||||
cancelButton.tintColor = Colors.text
|
cancelButton.tintColor = Colors.text
|
||||||
|
cancelButton.accessibilityLabel = "Cancel button"
|
||||||
|
cancelButton.isAccessibilityElement = true
|
||||||
navigationItem.leftBarButtonItem = cancelButton
|
navigationItem.leftBarButtonItem = cancelButton
|
||||||
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleSaveDisplayNameButtonTapped))
|
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleSaveDisplayNameButtonTapped))
|
||||||
doneButton.tintColor = Colors.text
|
doneButton.tintColor = Colors.text
|
||||||
|
doneButton.accessibilityLabel = "Done button"
|
||||||
|
doneButton.isAccessibilityElement = true
|
||||||
navigationItem.rightBarButtonItem = doneButton
|
navigationItem.rightBarButtonItem = doneButton
|
||||||
} else {
|
} else {
|
||||||
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
|
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
|
||||||
closeButton.tintColor = Colors.text
|
closeButton.tintColor = Colors.text
|
||||||
|
closeButton.accessibilityLabel = "Close button"
|
||||||
|
closeButton.isAccessibilityElement = true
|
||||||
navigationItem.leftBarButtonItem = closeButton
|
navigationItem.leftBarButtonItem = closeButton
|
||||||
if #available(iOS 13, *) { // Pre iOS 13 the user can't switch actively but the app still responds to system changes
|
if #available(iOS 13, *) { // Pre iOS 13 the user can't switch actively but the app still responds to system changes
|
||||||
let appModeIcon = isDarkMode ? #imageLiteral(resourceName: "ic_dark_theme_on").withTintColor(.white) : #imageLiteral(resourceName: "ic_dark_theme_off").withTintColor(.black)
|
let appModeIcon = isDarkMode ? #imageLiteral(resourceName: "ic_dark_theme_on").withTintColor(.white) : #imageLiteral(resourceName: "ic_dark_theme_off").withTintColor(.black)
|
||||||
|
@ -250,11 +261,13 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
||||||
appModeButton.setImage(appModeIcon, for: UIControl.State.normal)
|
appModeButton.setImage(appModeIcon, for: UIControl.State.normal)
|
||||||
appModeButton.tintColor = Colors.text
|
appModeButton.tintColor = Colors.text
|
||||||
appModeButton.addTarget(self, action: #selector(switchAppMode), for: UIControl.Event.touchUpInside)
|
appModeButton.addTarget(self, action: #selector(switchAppMode), for: UIControl.Event.touchUpInside)
|
||||||
|
appModeButton.accessibilityLabel = "Switch app mode button"
|
||||||
let qrCodeIcon = isDarkMode ? #imageLiteral(resourceName: "QRCode").withTintColor(.white) : #imageLiteral(resourceName: "QRCode").withTintColor(.black)
|
let qrCodeIcon = isDarkMode ? #imageLiteral(resourceName: "QRCode").withTintColor(.white) : #imageLiteral(resourceName: "QRCode").withTintColor(.black)
|
||||||
let qrCodeButton = UIButton()
|
let qrCodeButton = UIButton()
|
||||||
qrCodeButton.setImage(qrCodeIcon, for: UIControl.State.normal)
|
qrCodeButton.setImage(qrCodeIcon, for: UIControl.State.normal)
|
||||||
qrCodeButton.tintColor = Colors.text
|
qrCodeButton.tintColor = Colors.text
|
||||||
qrCodeButton.addTarget(self, action: #selector(showQRCode), for: UIControl.Event.touchUpInside)
|
qrCodeButton.addTarget(self, action: #selector(showQRCode), for: UIControl.Event.touchUpInside)
|
||||||
|
qrCodeButton.accessibilityLabel = "Show QR code button"
|
||||||
let stackView = UIStackView(arrangedSubviews: [ appModeButton, qrCodeButton ])
|
let stackView = UIStackView(arrangedSubviews: [ appModeButton, qrCodeButton ])
|
||||||
stackView.axis = .horizontal
|
stackView.axis = .horizontal
|
||||||
stackView.spacing = Values.mediumSpacing
|
stackView.spacing = Values.mediumSpacing
|
||||||
|
|
Loading…
Reference in New Issue