mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-13 03:09:58 +02:00
Finish voice_over
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
Changes in 0.9.5 (2019-09-20)
|
||||
===============================================
|
||||
|
||||
Bug fix:
|
||||
* VoiceOver: RoomVC: Fix some missing accessibility labels for buttons (#2722).
|
||||
* VoiceOver: RoomVC: Make VoiceOver focus on the contextual menu when selecting an event (#2721).
|
||||
* VoiceOver: RoomVC: Do not lose the focus on the timeline when paginating (with 3 fingers) (#2720).
|
||||
* VoiceOver: RoomVC: No VoiceOver on media (#2726).
|
||||
|
||||
Changes in 0.9.4 (2019-09-13)
|
||||
===============================================
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@
|
||||
|
||||
// Chat
|
||||
"room_jump_to_first_unread" = "Jump to first unread message";
|
||||
"room_accessiblity_scroll_to_bottom" = "Scroll to bottom";
|
||||
"room_new_message_notification" = "%d new message";
|
||||
"room_new_messages_notification" = "%d new messages";
|
||||
"room_one_user_is_typing" = "%@ is typing…";
|
||||
@@ -317,6 +318,18 @@
|
||||
"room_resource_usage_limit_reached_message_2" = "some users will not be able to log in.";
|
||||
"room_resource_usage_limit_reached_message_contact_3" = " to get this limit increased.";
|
||||
"room_message_edits_history_title" = "Message edits";
|
||||
"room_accessibility_search" = "Search";
|
||||
"room_accessibility_integrations" = "Integrations";
|
||||
"room_accessibility_upload" = "Upload";
|
||||
"room_accessibility_call" = "Call";
|
||||
"room_accessibility_hangup" = "Hang up";
|
||||
|
||||
"media_type_accessibility_image" = "Image";
|
||||
"media_type_accessibility_audio" = "Audio";
|
||||
"media_type_accessibility_video" = "Video";
|
||||
"media_type_accessibility_location" = "Location";
|
||||
"media_type_accessibility_file" = "File";
|
||||
"media_type_accessibility_sticker" = "Sticker";
|
||||
|
||||
// Unknown devices
|
||||
"unknown_devices_alert_title" = "Room contains unknown devices";
|
||||
@@ -677,6 +690,9 @@
|
||||
"widget_integration_missing_user_id" = "Missing user_id in request.";
|
||||
"widget_integration_room_not_visible" = "Room %@ is not visible.";
|
||||
|
||||
// Widget Picker
|
||||
"widget_picker_title" = "Integrations";
|
||||
|
||||
// Share extension
|
||||
"share_extension_auth_prompt" = "Login in the main app to share content";
|
||||
"share_extension_failed_to_encrypt" = "Failed to send. Check in the main app the encryption settings for this room";
|
||||
|
||||
@@ -1374,6 +1374,30 @@ internal enum VectorL10n {
|
||||
internal static var mediaPickerTitle: String {
|
||||
return VectorL10n.tr("Vector", "media_picker_title")
|
||||
}
|
||||
/// Audio
|
||||
internal static var mediaTypeAccessibilityAudio: String {
|
||||
return VectorL10n.tr("Vector", "media_type_accessibility_audio")
|
||||
}
|
||||
/// File
|
||||
internal static var mediaTypeAccessibilityFile: String {
|
||||
return VectorL10n.tr("Vector", "media_type_accessibility_file")
|
||||
}
|
||||
/// Image
|
||||
internal static var mediaTypeAccessibilityImage: String {
|
||||
return VectorL10n.tr("Vector", "media_type_accessibility_image")
|
||||
}
|
||||
/// Location
|
||||
internal static var mediaTypeAccessibilityLocation: String {
|
||||
return VectorL10n.tr("Vector", "media_type_accessibility_location")
|
||||
}
|
||||
/// Sticker
|
||||
internal static var mediaTypeAccessibilitySticker: String {
|
||||
return VectorL10n.tr("Vector", "media_type_accessibility_sticker")
|
||||
}
|
||||
/// Video
|
||||
internal static var mediaTypeAccessibilityVideo: String {
|
||||
return VectorL10n.tr("Vector", "media_type_accessibility_video")
|
||||
}
|
||||
/// The Internet connection appears to be offline.
|
||||
internal static var networkOfflinePrompt: String {
|
||||
return VectorL10n.tr("Vector", "network_offline_prompt")
|
||||
@@ -1462,6 +1486,30 @@ internal enum VectorL10n {
|
||||
internal static var retry: String {
|
||||
return VectorL10n.tr("Vector", "retry")
|
||||
}
|
||||
/// Call
|
||||
internal static var roomAccessibilityCall: String {
|
||||
return VectorL10n.tr("Vector", "room_accessibility_call")
|
||||
}
|
||||
/// Hang up
|
||||
internal static var roomAccessibilityHangup: String {
|
||||
return VectorL10n.tr("Vector", "room_accessibility_hangup")
|
||||
}
|
||||
/// Integrations
|
||||
internal static var roomAccessibilityIntegrations: String {
|
||||
return VectorL10n.tr("Vector", "room_accessibility_integrations")
|
||||
}
|
||||
/// Search
|
||||
internal static var roomAccessibilitySearch: String {
|
||||
return VectorL10n.tr("Vector", "room_accessibility_search")
|
||||
}
|
||||
/// Upload
|
||||
internal static var roomAccessibilityUpload: String {
|
||||
return VectorL10n.tr("Vector", "room_accessibility_upload")
|
||||
}
|
||||
/// Scroll to bottom
|
||||
internal static var roomAccessiblityScrollToBottom: String {
|
||||
return VectorL10n.tr("Vector", "room_accessiblity_scroll_to_bottom")
|
||||
}
|
||||
/// Take photo or video
|
||||
internal static var roomActionCamera: String {
|
||||
return VectorL10n.tr("Vector", "room_action_camera")
|
||||
@@ -2974,6 +3022,10 @@ internal enum VectorL10n {
|
||||
internal static var widgetNoPowerToManage: String {
|
||||
return VectorL10n.tr("Vector", "widget_no_power_to_manage")
|
||||
}
|
||||
/// Integrations
|
||||
internal static var widgetPickerTitle: String {
|
||||
return VectorL10n.tr("Vector", "widget_picker_title")
|
||||
}
|
||||
/// You don't currently have any stickerpacks enabled.
|
||||
internal static var widgetStickerPickerNoStickerpacksAlert: String {
|
||||
return VectorL10n.tr("Vector", "widget_sticker_picker_no_stickerpacks_alert")
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
mxSession = theMXSession;
|
||||
roomId = theRoomId;
|
||||
|
||||
_alertController = [UIAlertController alertControllerWithTitle:@"Matrix Apps"
|
||||
_alertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"widget_picker_title", @"Vector", nil)
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
}
|
||||
|
||||
@@ -90,4 +90,9 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag)
|
||||
- (BOOL)showAllReactionsForEvent:(NSString*)eventId;
|
||||
- (void)setShowAllReactions:(BOOL)showAllReactions forEvent:(NSString*)eventId;
|
||||
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (NSString*)accessibilityLabel;
|
||||
|
||||
@end
|
||||
|
||||
@@ -698,4 +698,58 @@ static NSAttributedString *timestampVerticalWhitespace = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel
|
||||
{
|
||||
NSString *accessibilityLabel;
|
||||
|
||||
// Only media require manual handling for accessibility
|
||||
if (self.attachment)
|
||||
{
|
||||
NSString *mediaName = [self accessibilityLabelForAttachmentType:self.attachment.type];
|
||||
|
||||
MXJSONModelSetString(accessibilityLabel, self.events.firstObject.content[@"body"]);
|
||||
if (accessibilityLabel)
|
||||
{
|
||||
accessibilityLabel = [NSString stringWithFormat:@"%@ %@", mediaName, accessibilityLabel];
|
||||
}
|
||||
else
|
||||
{
|
||||
accessibilityLabel = mediaName;
|
||||
}
|
||||
}
|
||||
|
||||
return accessibilityLabel;
|
||||
}
|
||||
|
||||
- (NSString*)accessibilityLabelForAttachmentType:(MXKAttachmentType)attachmentType
|
||||
{
|
||||
NSString *accessibilityLabel;
|
||||
switch (attachmentType)
|
||||
{
|
||||
case MXKAttachmentTypeImage:
|
||||
accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_image", @"Vector", nil);
|
||||
break;
|
||||
case MXKAttachmentTypeAudio:
|
||||
accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_audio", @"Vector", nil);
|
||||
break;
|
||||
case MXKAttachmentTypeVideo:
|
||||
accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_video", @"Vector", nil);
|
||||
break;
|
||||
case MXKAttachmentTypeLocation:
|
||||
accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_location", @"Vector", nil);
|
||||
break;
|
||||
case MXKAttachmentTypeFile:
|
||||
accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_file", @"Vector", nil);
|
||||
break;
|
||||
case MXKAttachmentTypeSticker:
|
||||
accessibilityLabel = NSLocalizedStringFromTable(@"media_type_accessibility_sticker", @"Vector", nil);
|
||||
break;
|
||||
default:
|
||||
accessibilityLabel = @"";
|
||||
break;
|
||||
}
|
||||
|
||||
return accessibilityLabel;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -93,20 +93,30 @@ final class ContextualMenuItemView: UIView, NibOwnerLoadable {
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func fill(title: String, image: UIImage?) {
|
||||
self.originalImage = image?.withRenderingMode(.alwaysTemplate)
|
||||
self.titleLabel.text = title
|
||||
self.updateView()
|
||||
}
|
||||
|
||||
|
||||
func fill(menuItem: RoomContextualMenuItem) {
|
||||
self.fill(title: menuItem.title, image: menuItem.image)
|
||||
self.setupAccessibility(title: menuItem.title, isEnabled: menuItem.isEnabled)
|
||||
self.action = menuItem.action
|
||||
self.isEnabled = menuItem.isEnabled
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func fill(title: String, image: UIImage?) {
|
||||
self.originalImage = image?.withRenderingMode(.alwaysTemplate)
|
||||
self.titleLabel.text = title
|
||||
self.updateView()
|
||||
}
|
||||
|
||||
private func setupAccessibility(title: String, isEnabled: Bool) {
|
||||
self.isAccessibilityElement = true
|
||||
self.accessibilityLabel = title
|
||||
self.accessibilityTraits = .button
|
||||
if !isEnabled {
|
||||
self.accessibilityTraits.insert(.notEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupGestureRecognizer() {
|
||||
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(buttonAction(_:)))
|
||||
|
||||
@@ -117,11 +117,15 @@ final class RoomContextualMenuViewController: UIViewController, Themable {
|
||||
func showMenuToolbar() {
|
||||
self.menuToolbarViewBottomConstraint.constant = 0
|
||||
self.menuToolbarView.alpha = 1
|
||||
|
||||
// Force VoiceOver to focus on the menu bar actions
|
||||
UIAccessibility.post(notification: .screenChanged, argument: self.menuToolbarView)
|
||||
}
|
||||
|
||||
func hideMenuToolbar() {
|
||||
self.menuToolbarViewBottomConstraint.constant = self.hiddenToolbarViewBottomConstant
|
||||
self.menuToolbarView.alpha = 0
|
||||
UIAccessibility.post(notification: .screenChanged, argument: nil)
|
||||
}
|
||||
|
||||
func prepareReactionsMenuAnimations() {
|
||||
|
||||
@@ -196,6 +196,8 @@
|
||||
if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
|
||||
{
|
||||
MXKRoomBubbleTableViewCell *bubbleCell = (MXKRoomBubbleTableViewCell*)cell;
|
||||
[self resetAccessibilityForCell:bubbleCell];
|
||||
|
||||
RoomBubbleCellData *cellData = (RoomBubbleCellData*)bubbleCell.bubbleData;
|
||||
NSArray *bubbleComponents = cellData.bubbleComponents;
|
||||
|
||||
@@ -507,6 +509,8 @@
|
||||
|
||||
// Auto animate the sticker in case of animated gif
|
||||
bubbleCell.isAutoAnimatedGif = (cellData.attachment && cellData.attachment.type == MXKAttachmentTypeSticker);
|
||||
|
||||
[self setupAccessibilityForCell:bubbleCell withCellData:cellData];
|
||||
}
|
||||
|
||||
return cell;
|
||||
@@ -563,6 +567,35 @@
|
||||
[self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure];
|
||||
}
|
||||
|
||||
|
||||
#pragma - Accessibility
|
||||
|
||||
- (void)setupAccessibilityForCell:(MXKRoomBubbleTableViewCell *)cell withCellData:(RoomBubbleCellData*)cellData
|
||||
{
|
||||
// Set accessibility only on media. Let VoiceOver automatically manages text messages
|
||||
if (cellData.attachment)
|
||||
{
|
||||
NSString *accessibilityLabel = [cellData accessibilityLabel];
|
||||
if (cell.messageTextView.text.length)
|
||||
{
|
||||
// Files are presented as text with link
|
||||
cell.messageTextView.accessibilityLabel = accessibilityLabel;
|
||||
cell.messageTextView.isAccessibilityElement = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.attachmentView.accessibilityLabel = accessibilityLabel;
|
||||
cell.attachmentView.isAccessibilityElement = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetAccessibilityForCell:(MXKRoomBubbleTableViewCell *)cell
|
||||
{
|
||||
cell.messageTextView.accessibilityLabel = nil;
|
||||
cell.attachmentView.accessibilityLabel = nil;
|
||||
}
|
||||
|
||||
#pragma mark - BubbleReactionsViewModelDelegate
|
||||
|
||||
- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didAddReaction:(MXReactionCount *)reactionCount forEventId:(NSString *)eventId
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
#import "Riot-Swift.h"
|
||||
|
||||
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
||||
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
||||
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
|
||||
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate>
|
||||
{
|
||||
@@ -813,6 +813,92 @@
|
||||
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
// Handle scrolling when VoiceOver is on because it does not work well if we let the system do:
|
||||
// VoiceOver loses the focus on the tableview
|
||||
- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction
|
||||
{
|
||||
BOOL canScroll = YES;
|
||||
|
||||
// Scroll by one page
|
||||
CGFloat tableViewHeight = self.bubblesTableView.frame.size.height;
|
||||
|
||||
CGPoint offset = self.bubblesTableView.contentOffset;
|
||||
switch (direction)
|
||||
{
|
||||
case UIAccessibilityScrollDirectionUp:
|
||||
offset.y -= tableViewHeight;
|
||||
break;
|
||||
|
||||
case UIAccessibilityScrollDirectionDown:
|
||||
offset.y += tableViewHeight;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset.y < 0 && ![self.roomDataSource.timeline canPaginate:MXTimelineDirectionBackwards])
|
||||
{
|
||||
// Can't paginate more. Let's stick on the first item
|
||||
UIView *focusedView = [self firstCellWithAccessibilityDataInCells:self.bubblesTableView.visibleCells.objectEnumerator];
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, focusedView);
|
||||
canScroll = NO;
|
||||
}
|
||||
else if (offset.y > self.bubblesTableView.contentSize.height - tableViewHeight
|
||||
&& ![self.roomDataSource.timeline canPaginate:MXTimelineDirectionForwards])
|
||||
{
|
||||
// Can't paginate more. Let's stick on the last item with accessibility
|
||||
UIView *focusedView = [self firstCellWithAccessibilityDataInCells:self.bubblesTableView.visibleCells.reverseObjectEnumerator];
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, focusedView);
|
||||
canScroll = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disable VoiceOver while scrolling
|
||||
self.bubblesTableView.accessibilityElementsHidden = YES;
|
||||
|
||||
[self.bubblesTableView setContentOffset:offset animated:NO];
|
||||
|
||||
NSEnumerator<UITableViewCell*> *cells;
|
||||
if (direction == UIAccessibilityScrollDirectionUp)
|
||||
{
|
||||
cells = self.bubblesTableView.visibleCells.objectEnumerator;
|
||||
}
|
||||
else
|
||||
{
|
||||
cells = self.bubblesTableView.visibleCells.reverseObjectEnumerator;
|
||||
}
|
||||
UIView *cell = [self firstCellWithAccessibilityDataInCells:cells];
|
||||
|
||||
self.bubblesTableView.accessibilityElementsHidden = NO;
|
||||
|
||||
// Force VoiceOver to focus on a visible item
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, cell);
|
||||
}
|
||||
|
||||
// If we cannot scroll, let VoiceOver indicates the border
|
||||
return canScroll;
|
||||
}
|
||||
|
||||
- (UIView*)firstCellWithAccessibilityDataInCells:(NSEnumerator<UITableViewCell*>*)cells
|
||||
{
|
||||
UIView *view;
|
||||
|
||||
for (UITableViewCell *cell in cells)
|
||||
{
|
||||
if (![cell isKindOfClass:[RoomEmptyBubbleCell class]])
|
||||
{
|
||||
view = cell;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Override MXKRoomViewController
|
||||
|
||||
- (void)onMatrixSessionChange
|
||||
@@ -1372,12 +1458,16 @@
|
||||
icon = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
|
||||
self.navigationItem.rightBarButtonItems[1].image = icon;
|
||||
self.navigationItem.rightBarButtonItems[1].accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_integrations", @"Vector", nil);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset original icon
|
||||
self.navigationItem.rightBarButtonItems[1].image = [UIImage imageNamed:@"apps-icon"];
|
||||
self.navigationItem.rightBarButtonItems[1].accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_integrations", @"Vector", nil);
|
||||
}
|
||||
|
||||
self.navigationItem.rightBarButtonItems.firstObject.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_search", @"Vector", nil);
|
||||
}
|
||||
|
||||
// Do not change title view class here if the expanded header is visible.
|
||||
|
||||
@@ -307,6 +307,11 @@
|
||||
self.iconImageView.image = [UIImage imageNamed:@"scrolldown"];
|
||||
}
|
||||
self.iconImageView.hidden = NO;
|
||||
|
||||
// Make VoiceOver consider it as a button
|
||||
self.iconImageView.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessiblity_scroll_to_bottom", @"Vector", nil);
|
||||
self.iconImageView.isAccessibilityElement = YES;
|
||||
self.iconImageView.accessibilityTraits = UIAccessibilityTraitButton;
|
||||
|
||||
if (onIconTapGesture)
|
||||
{
|
||||
|
||||
@@ -92,6 +92,10 @@
|
||||
growingTextView.tintColor = ThemeService.shared.theme.tintColor;
|
||||
|
||||
growingTextView.internalTextView.keyboardAppearance = ThemeService.shared.theme.keyboardAppearance;
|
||||
|
||||
self.attachMediaButton.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_upload", @"Vector", nil);
|
||||
self.voiceCallButton.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_call", @"Vector", nil);
|
||||
self.hangupCallButton.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_hangup", @"Vector", nil);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.9.4</string>
|
||||
<string>0.9.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.9.4</string>
|
||||
<string>0.9.5</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<true/>
|
||||
<key>ITSEncryptionExportComplianceCode</key>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.9.4</string>
|
||||
<string>0.9.5</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.9.4</string>
|
||||
<string>0.9.5</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.9.4</string>
|
||||
<string>0.9.5</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.9.4</string>
|
||||
<string>0.9.5</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
|
||||
Reference in New Issue
Block a user