Merge pull request #5014 from vector-im/stefan/messageForwarding

Message forwarding
This commit is contained in:
Stefan Ceriu 2021-10-19 16:17:07 +03:00 committed by GitHub
commit 6f7a9145e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2015 additions and 1842 deletions

View file

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -360,6 +360,7 @@ Tap the + to start adding people.";
"room_event_action_redact" = "Remove";
"room_event_action_more" = "More";
"room_event_action_share" = "Share";
"room_event_action_forward" = "Forward";
"room_event_action_permalink" = "Permalink";
"room_event_action_view_source" = "View Source";
"room_event_action_view_decrypted_source" = "View Decrypted Source";

View file

@ -211,6 +211,8 @@ internal enum Asset {
internal static let cancel = ImageAsset(name: "cancel")
internal static let e2eVerified = ImageAsset(name: "e2e_verified")
internal static let horizontalLogo = ImageAsset(name: "horizontal_logo")
internal static let radioButtonDefault = ImageAsset(name: "radio-button-default")
internal static let radioButtonSelected = ImageAsset(name: "radio-button-selected")
}
}
// swiftlint:enable identifier_name line_length nesting type_body_length type_name

View file

@ -2887,6 +2887,10 @@ public class VectorL10n: NSObject {
public static var roomEventActionEdit: String {
return VectorL10n.tr("Vector", "room_event_action_edit")
}
/// Forward
public static var roomEventActionForward: String {
return VectorL10n.tr("Vector", "room_event_action_forward")
}
/// Reason for kicking this user
public static var roomEventActionKickPromptReason: String {
return VectorL10n.tr("Vector", "room_event_action_kick_prompt_reason")

View file

@ -16,7 +16,11 @@
#import "BuildInfo.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
#define MAKE_STRING(x) #x
#define MAKE_NS_STRING(x) @MAKE_STRING(x)

View file

@ -16,6 +16,8 @@
limitations under the License.
*/
@import MobileCoreServices;
#import "RoomViewController.h"
#import "RoomDataSource.h"
@ -106,6 +108,7 @@
#import "AvatarGenerator.h"
#import "Tools.h"
#import "WidgetManager.h"
#import "ShareManager.h"
#import "GBDeviceInfo_iOS.h"
@ -249,6 +252,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
@property (nonatomic, strong) VoiceMessageController *voiceMessageController;
@property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter;
@property (nonatomic, strong) ShareManager *shareManager;
@property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator;
@property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView;
@ -2394,10 +2399,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
// Set a default title view class without handling tap gesture (Let [self refreshRoomTitle] refresh this view correctly).
[self setRoomTitleViewClass:RoomTitleView.class];
// Remove details icon
RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView;
// Remove the shadow image used to hide the bottom border of the navigation bar when the preview header is displayed
[mainNavigationController.navigationBar setShadowImage:nil];
[mainNavigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
@ -3190,6 +3192,23 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
}]];
}
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
self.shareManager = [[ShareManager alloc] initWithShareItemProvider:[[SimpleShareItemProvider alloc] initWithTextMessage:selectedComponent.textMessage]
type:ShareManagerTypeForward];
MXWeakify(self);
[self.shareManager setCompletionCallback:^(ShareManagerResult result) {
MXStrongifyAndReturnIfNil(self);
[attachment onShareEnded];
[self dismissViewControllerAnimated:YES completion:nil];
self.shareManager = nil;
}];
[self presentViewController:self.shareManager.mainViewController animated:YES completion:nil];
}]];
if (!isJitsiCallEvent)
{
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote]
@ -3243,6 +3262,29 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
}
else // Add action for attachment
{
if (attachment.type == MXKAttachmentTypeFile ||
attachment.type == MXKAttachmentTypeImage ||
attachment.type == MXKAttachmentTypeVideo ||
attachment.type == MXKAttachmentTypeVoiceMessage) {
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
self.shareManager = [[ShareManager alloc] initWithShareItemProvider:[[SimpleShareItemProvider alloc] initWithAttachment:attachment]
type:ShareManagerTypeForward];
MXWeakify(self);
[self.shareManager setCompletionCallback:^(ShareManagerResult result) {
MXStrongifyAndReturnIfNil(self);
[attachment onShareEnded];
[self dismissViewControllerAnimated:YES completion:nil];
self.shareManager = nil;
}];
[self presentViewController:self.shareManager.mainViewController animated:YES completion:nil];
}]];
}
if (BuildSettings.messageDetailsAllowSave)
{
if (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo)
@ -3340,7 +3382,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self cancelEventSelection];
[self startActivityIndicator];
[attachment prepareShare:^(NSURL *fileURL) {
[self stopActivityIndicator];
__strong __typeof(weakSelf)self = weakSelf;
self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
@ -3355,10 +3400,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
}
} failure:^(NSError *error) {
//Alert user
[self showError:error];
[self stopActivityIndicator];
}];
// Start animation in case of download during attachment preparing

View file

@ -66,6 +66,7 @@ targets:
excludes:
- "Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift"
- "**/*.strings" # Exclude all strings files
- path: ../RiotShareExtension/Shared
# Add separately localizable files
# Once a language has enough translations (>80%), it must be declared here

View file

@ -1,130 +0,0 @@
/*
Copyright 2017 Aram Sargsyan
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/Foundation.h>
#import <MatrixKit/MatrixKit.h>
@class ShareExtensionManager;
@class SharePresentingViewController;
@protocol Configurable;
/**
Posted when the matrix user account and his data has been checked and updated.
The notification object is the MXKAccount instance.
*/
extern NSString *const kShareExtensionManagerDidUpdateAccountDataNotification;
/**
The protocol for the manager's delegate
*/
@protocol ShareExtensionManagerDelegate <NSObject>
@required
/**
Called when an image is going to be shared to show a compression prompt
@param extensionManager the ShareExtensionManager object that called the method
@param compressionPrompt the prompt that was prepared for the image which is going to be shared
*/
- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager showImageCompressionPrompt:(UIAlertController *)compressionPrompt;
@optional
/**
Called when the manager starts sending the content to a room
@param extensionManager the ShareExtensionManager object that called the method
@param room the room where content will be sent
*/
- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager didStartSendingContentToRoom:(MXRoom *)room;
/**
Called when the progress of the uploading media changes
@param extensionManager the ShareExtensionManager object that called the method
@param progress the current progress
*/
- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager mediaUploadProgress:(CGFloat)progress;
@end
/**
A class used to share content from the extension
*/
@interface ShareExtensionManager : NSObject
/**
The share extension context that represents a user's sharing request, also stores the content to be shared
*/
@property (nonatomic) NSExtensionContext *shareExtensionContext;
/**
The share app extensions primary view controller.
*/
@property (nonatomic) SharePresentingViewController *primaryViewController;
/**
The current user account
*/
@property (nonatomic, readonly) MXKAccount *userAccount;
/**
The shared file store
*/
@property (nonatomic, readonly) MXFileStore *fileStore;
/**
A delegate used to notify about needed UI changes when sharing
*/
@property (nonatomic, weak) id<ShareExtensionManagerDelegate> delegate;
// Build Settings
@property (nonatomic, readonly) id<Configurable> configuration;
/**
The singleton instance
*/
+ (instancetype)sharedManager;
/**
Send the content that the user has chosen to a room
@param room the room to send the content to
@param failureBlock the code to be executed when sharing has failed for whatever reason
note: there is no "successBlock" parameter because when the sharing succeeds, the extension needs to close itself
*/
- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock;
/**
Checks if there is an image in the user chosen content
@return YES if there is, NO otherwise
*/
- (BOOL)hasImageTypeContent;
/**
Terminate the extension and return to the app that started it
@param canceled YES if the user chose to cancel the sharing, NO otherwise
*/
- (void)terminateExtensionCanceled:(BOOL)canceled;
@end
@interface NSItemProvider (ShareExtensionManager)
@property BOOL isLoaded;
@end

File diff suppressed because it is too large Load diff

View file

@ -1,79 +0,0 @@
/*
Copyright 2017 Aram Sargsyan
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 "SharePresentingViewController.h"
#import "ShareViewController.h"
#import "ShareExtensionManager.h"
#import "ThemeService.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
@interface SharePresentingViewController ()
@property (nonatomic) ShareViewController *shareViewController;
@end
@implementation SharePresentingViewController
- (void)viewDidLoad
{
[super viewDidLoad];
ShareExtensionManager *sharedManager = [ShareExtensionManager sharedManager];
sharedManager.primaryViewController = self;
sharedManager.shareExtensionContext = self.extensionContext;
// Set up current theme
ThemeService.shared.themeId = RiotSettings.shared.userInterfaceTheme;
[self presentShareViewController];
}
- (void)destroy
{
if (self.shareViewController)
{
[self.shareViewController destroy];
self.shareViewController = nil;
}
}
- (void)presentShareViewController
{
self.shareViewController = [[ShareViewController alloc] init];
self.shareViewController.providesPresentationContextTransitionStyle = YES;
self.shareViewController.definesPresentationContext = YES;
self.shareViewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
self.shareViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:self.shareViewController animated:YES completion:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

View file

@ -1,22 +0,0 @@
/*
Copyright 2017 Aram Sargsyan
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 <Social/Social.h>
#import <MatrixKit/MatrixKit.h>
@interface ShareViewController : MXKViewController
@end

View file

@ -1,182 +0,0 @@
/*
Copyright 2017 Aram Sargsyan
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 "ShareViewController.h"
#import "SegmentedViewController.h"
#import "RoomsListViewController.h"
#import "FallbackViewController.h"
#import "ShareDataSource.h"
#import "ShareExtensionManager.h"
#import "ThemeService.h"
#import "RiotShareExtension-Swift.h"
@interface ShareViewController ()
@property (weak, nonatomic) IBOutlet UIView *masterContainerView;
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UIView *contentView;
@property (nonatomic) SegmentedViewController *segmentedViewController;
@property (nonatomic) id shareExtensionManagerDidUpdateAccountDataObserver;
@end
@implementation ShareViewController
#pragma mark - Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.tintColor = ThemeService.shared.theme.tintColor;
self.titleLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.masterContainerView.backgroundColor = ThemeService.shared.theme.baseColor;
self.shareExtensionManagerDidUpdateAccountDataObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kShareExtensionManagerDidUpdateAccountDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self configureViews];
}];
[self configureViews];
}
- (void)destroy
{
[super destroy];
if (self.shareExtensionManagerDidUpdateAccountDataObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:self.shareExtensionManagerDidUpdateAccountDataObserver];
self.shareExtensionManagerDidUpdateAccountDataObserver = nil;
}
[self resetContentView];
}
- (void)resetContentView
{
// Empty the content view
NSArray *subviews = self.contentView.subviews;
for (UIView *subview in subviews)
{
[subview removeFromSuperview];
}
// Release the current segmented view controller if any
if (self.segmentedViewController)
{
[self.segmentedViewController removeFromParentViewController];
// Release correctly all the existing data source and view controllers.
[self.segmentedViewController destroy];
self.segmentedViewController = nil;
}
}
#pragma mark - Private
- (void)configureViews
{
self.masterContainerView.layer.cornerRadius = 7;
[self resetContentView];
if ([ShareExtensionManager sharedManager].userAccount)
{
self.titleLabel.text = [VectorL10n sendTo:@""];
[self configureSegmentedViewController];
}
else
{
NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
NSString *bundleDisplayName = infoDictionary[@"CFBundleDisplayName"];
self.titleLabel.text = bundleDisplayName;
[self configureFallbackViewController];
}
}
- (void)configureSegmentedViewController
{
self.segmentedViewController = [SegmentedViewController segmentedViewController];
NSArray *titles = @[[VectorL10n titleRooms], [VectorL10n titlePeople]];
void (^failureBlock)(void) = ^void() {
[self dismissViewControllerAnimated:YES completion:^{
[[ShareExtensionManager sharedManager] terminateExtensionCanceled:NO];
}];
};
ShareDataSource *roomsDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModeRooms];
RoomsListViewController *roomsViewController = [RoomsListViewController recentListViewController];
roomsViewController.failureBlock = failureBlock;
[roomsViewController displayList:roomsDataSource];
ShareDataSource *peopleDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModePeople];
RoomsListViewController *peopleViewController = [RoomsListViewController recentListViewController];
peopleViewController.failureBlock = failureBlock;
[peopleViewController displayList:peopleDataSource];
[self.segmentedViewController initWithTitles:titles viewControllers:@[roomsViewController, peopleViewController] defaultSelected:0];
[self addChildViewController:self.segmentedViewController];
[self.contentView addSubview:self.segmentedViewController.view];
[self.segmentedViewController didMoveToParentViewController:self];
[self autoPinSubviewEdges:self.segmentedViewController.view toSuperviewEdges:self.contentView];
}
- (void)configureFallbackViewController
{
FallbackViewController *fallbackVC = [FallbackViewController new];
[self addChildViewController:fallbackVC];
[self.contentView addSubview:fallbackVC.view];
[fallbackVC didMoveToParentViewController:self];
[self autoPinSubviewEdges:fallbackVC.view toSuperviewEdges:self.contentView];
}
- (void)autoPinSubviewEdges:(UIView *)subview toSuperviewEdges:(UIView *)superview
{
subview.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
widthConstraint.active = YES;
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
heightConstraint.active = YES;
NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
centerXConstraint.active = YES;
NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
centerYConstraint.active = YES;
}
#pragma mark - Actions
- (IBAction)close:(UIButton *)sender
{
[self dismissViewControllerAnimated:YES completion:^{
[[ShareExtensionManager sharedManager] terminateExtensionCanceled:YES];
}];
}
@end

View file

@ -16,23 +16,25 @@
#import <MatrixKit/MatrixKit.h>
typedef NS_ENUM(NSInteger, ShareDataSourceMode)
{
DataSourceModePeople,
DataSourceModeRooms
};
@class ShareDataSource;
@protocol ShareDataSourceDelegate <NSObject>
- (void)shareDataSourceDidChangeSelectedRoomIdentifiers:(ShareDataSource *)shareDataSource;
@end
@interface ShareDataSource : MXKRecentsDataSource
- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode;
@property (nonatomic, weak) id<ShareDataSourceDelegate> shareDelegate;
/**
Returns the cell data at the index path
@param indexPath the index of the cell
@return the MXKRecentCellData instance if it exists
*/
- (MXKRecentCellData *)cellDataAtIndexPath:(NSIndexPath *)indexPath;
@property (nonatomic, strong, readonly) NSSet<NSString *> *selectedRoomIdentifiers;
- (instancetype)initWithFileStore:(MXFileStore *)fileStore
credentials:(MXCredentials *)credentials;
- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated;
- (void)deselectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated;
@end

View file

@ -15,26 +15,31 @@
*/
#import "ShareDataSource.h"
#import "ShareExtensionManager.h"
#import "RecentRoomTableViewCell.h"
@interface ShareDataSource ()
@property (nonatomic, readwrite) ShareDataSourceMode dataSourceMode;
@property (nonatomic, strong, readonly) MXFileStore *fileStore;
@property (nonatomic, strong, readonly) MXCredentials *credentials;
@property NSArray <MXKRecentCellData *> *recentCellDatas;
@property NSMutableArray <MXKRecentCellData *> *visibleRoomCellDatas;
@property (nonatomic, strong) NSMutableSet<NSString *> *internalSelectedRoomIdentifiers;
@end
@implementation ShareDataSource
- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode
- (instancetype)initWithFileStore:(MXFileStore *)fileStore
credentials:(MXCredentials *)credentials
{
self = [super init];
if (self)
if (self = [super init])
{
self.dataSourceMode = dataSourceMode;
_fileStore = fileStore;
_credentials = credentials;
_internalSelectedRoomIdentifiers = [NSMutableSet set];
[self loadCellData];
}
@ -49,20 +54,39 @@
_visibleRoomCellDatas = nil;
}
- (NSSet<NSString *> *)selectedRoomIdentifiers
{
return self.internalSelectedRoomIdentifiers.copy;
}
- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated
{
[self.internalSelectedRoomIdentifiers addObject:roomIdentifier];
[self.shareDelegate shareDataSourceDidChangeSelectedRoomIdentifiers:self];
}
- (void)deselectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated
{
[self.internalSelectedRoomIdentifiers removeObject:roomIdentifier];
[self.shareDelegate shareDataSourceDidChangeSelectedRoomIdentifiers:self];
}
#pragma mark - Private
- (void)loadCellData
{
[[ShareExtensionManager sharedManager].fileStore asyncRoomsSummaries:^(NSArray<MXRoomSummary *> * _Nonnull roomsSummaries) {
[self.fileStore asyncRoomsSummaries:^(NSArray<MXRoomSummary *> *roomsSummaries) {
NSMutableArray *cellData = [NSMutableArray array];
// Add a fake matrix session to each room summary to provide it a REST client (used to handle correctly the room avatar).
MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:[ShareExtensionManager sharedManager].userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]];
MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:self.credentials andOnUnrecognizedCertificateBlock:nil]];
for (MXRoomSummary *roomSummary in roomsSummaries)
{
if (!roomSummary.hiddenFromUser && ((self.dataSourceMode == DataSourceModeRooms) ^ roomSummary.isDirect))
if (!roomSummary.hiddenFromUser)
{
[roomSummary setMatrixSession:session];
@ -133,6 +157,7 @@
{
self.visibleRoomCellDatas = nil;
}
[self.delegate dataSource:self didCellChange:nil];
}
@ -156,7 +181,11 @@
{
RecentRoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[RecentRoomTableViewCell defaultReuseIdentifier]];
[cell render:[self cellDataAtIndexPath:indexPath]];
MXKRecentCellData *data = [self cellDataAtIndexPath:indexPath];
[cell render:data];
[cell setCustomSelected:[self.selectedRoomIdentifiers containsObject:data.roomSummary.roomId] animated:NO];
return cell;
}

View file

@ -0,0 +1,35 @@
//
// Copyright 2021 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 public enum ShareItemType: UInt {
case fileURL, text, URL, image, video, movie, voiceMessage, unknown
}
@objc public protocol ShareItemProtocol {
var type: ShareItemType { get }
}
@objc public protocol ShareItemProviderProtocol {
var items: [ShareItemProtocol] { get }
func areAllItemsImages() -> Bool
func areAllItemsLoaded() -> Bool
func loadItem(_ item: ShareItemProtocol, completion: @escaping (Any?, Error?) -> Void)
}

View file

@ -0,0 +1,109 @@
//
// Copyright 2021 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
private class SimpleShareItem: ShareItemProtocol {
let attachment: MXKAttachment?
let textMessage: String?
init(withAttachment attachment: MXKAttachment) {
self.attachment = attachment
self.textMessage = nil
}
init(withTextMessage textMessage: String) {
self.attachment = nil
self.textMessage = textMessage
}
var type: ShareItemType {
guard textMessage == nil else {
return .text
}
guard let attachment = attachment else {
return .unknown
}
if attachment.type == MXKAttachmentTypeImage {
return .image
} else if attachment.type == MXKAttachmentTypeVideo {
return .video
} else if attachment.type == MXKAttachmentTypeFile {
return .fileURL
} else if attachment.type == MXKAttachmentTypeVoiceMessage {
return .voiceMessage
} else {
return .unknown
}
}
}
@objc class SimpleShareItemProvider: NSObject, ShareItemProviderProtocol {
private let attachment: MXKAttachment?
private let textMessage: String?
let items: [ShareItemProtocol]
private override init() {
attachment = nil
textMessage = nil
self.items = []
}
@objc public init(withAttachment attachment: MXKAttachment) {
self.attachment = attachment
self.items = [SimpleShareItem(withAttachment: attachment)];
self.textMessage = nil
}
@objc public init(withTextMessage textMessage: String) {
self.textMessage = textMessage
self.items = [SimpleShareItem(withTextMessage: textMessage)];
self.attachment = nil
}
func loadItem(_ item: ShareItemProtocol, completion: @escaping (Any?, Error?) -> Void) {
if let textMessage = self.textMessage {
completion(textMessage, nil)
return
}
guard let attachment = attachment else {
fatalError("[SimpleShareItemProvider] Invalid item provider state.")
}
attachment.prepareShare({ url in
DispatchQueue.main.async {
completion(url, nil)
}
}, failure: { error in
DispatchQueue.main.async {
completion(nil, error)
}
})
}
func areAllItemsLoaded() -> Bool {
return true
}
func areAllItemsImages() -> Bool {
return (attachment != nil && attachment?.type == MXKAttachmentTypeImage)
}
}

View file

@ -0,0 +1,46 @@
/*
Copyright 2017 Aram Sargsyan
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 <MatrixKit/MatrixKit.h>
@protocol ShareItemProviderProtocol;
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, ShareManagerType) {
ShareManagerTypeSend,
ShareManagerTypeForward,
};
typedef NS_ENUM(NSUInteger, ShareManagerResult) {
ShareManagerResultFinished,
ShareManagerResultCancelled,
ShareManagerResultFailed
};
@interface ShareManager : NSObject
@property (nonatomic, copy) void (^completionCallback)(ShareManagerResult);
- (instancetype)initWithShareItemProvider:(id<ShareItemProviderProtocol>)shareItemProvider
type:(ShareManagerType)type;
- (UIViewController *)mainViewController;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,8 @@
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
@import UIKit;
@interface FallbackViewController : MXKViewController
@interface FallbackViewController : UIViewController
@end

View file

@ -42,10 +42,4 @@
self.logoImageView.tintColor = ThemeService.shared.theme.tintColor;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

View file

@ -20,4 +20,6 @@
+ (CGFloat)cellHeight;
- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated;
@end

View file

@ -18,13 +18,19 @@
#import "MXRoomSummary+Riot.h"
#import "ThemeService.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
@interface RecentRoomTableViewCell ()
@property (weak, nonatomic) IBOutlet MXKImageView *avatarImageView;
@property (weak, nonatomic) IBOutlet UILabel *roomTitleLabel;
@property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon;
@property (weak, nonatomic) IBOutlet UIButton *selectionButton;
@end
@ -51,6 +57,12 @@
self.roomTitleLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
self.contentView.backgroundColor = ThemeService.shared.theme.backgroundColor;
[self.selectionButton setImage:[UIImage imageNamed:@"radio-button-default"] forState:UIControlStateNormal];
[self.selectionButton setImage:[UIImage imageNamed:@"radio-button-selected"] forState:UIControlStateSelected];
[self.selectionButton setTitle:@"" forState:UIControlStateNormal];
[self.selectionButton setTitle:@"" forState:UIControlStateSelected];
}
- (void)layoutSubviews
@ -87,4 +99,11 @@
return 74;
}
- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated
{
[UIView animateWithDuration:(animated ? 0.25f : 0.0f) animations:^{
[self.selectionButton setSelected:selected];
}];
}
@end

View file

@ -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="19162" 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="19144"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -25,12 +25,9 @@
<constraint firstAttribute="width" constant="42" id="Xp0-5S-1wI"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vBS-iO-8H6">
<rect key="frame" x="78" y="27" width="33" height="20"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Text" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vBS-iO-8H6">
<rect key="frame" x="76" y="15" width="32" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="TitleLabel"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="H21-1K-fGz"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -43,15 +40,27 @@
<constraint firstAttribute="width" constant="11" id="Mai-xD-TqV"/>
</constraints>
</imageView>
<button opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m0f-of-6xq">
<rect key="frame" x="562" y="29" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Wue-Ih-eVg"/>
<constraint firstAttribute="width" constant="16" id="s5a-PW-58p"/>
</constraints>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" image="radio-button-selected"/>
</button>
</subviews>
<constraints>
<constraint firstItem="vBS-iO-8H6" firstAttribute="top" secondItem="b83-aU-AJ3" secondAttribute="topMargin" constant="4" id="0nI-h1-enE"/>
<constraint firstItem="HJd-Am-cCx" firstAttribute="leading" secondItem="b83-aU-AJ3" secondAttribute="leadingMargin" constant="6" id="4df-2f-865"/>
<constraint firstItem="m0f-of-6xq" firstAttribute="centerY" secondItem="b83-aU-AJ3" secondAttribute="centerY" id="B3g-N6-bh1"/>
<constraint firstItem="hqD-YY-LWz" firstAttribute="top" secondItem="b83-aU-AJ3" secondAttribute="topMargin" constant="34" id="Cdm-v3-js8"/>
<constraint firstItem="hqD-YY-LWz" firstAttribute="leading" secondItem="b83-aU-AJ3" secondAttribute="leadingMargin" constant="42" id="UIE-13-LGw"/>
<constraint firstItem="m0f-of-6xq" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vBS-iO-8H6" secondAttribute="trailing" constant="16" id="dyz-rB-vUR"/>
<constraint firstItem="HJd-Am-cCx" firstAttribute="centerY" secondItem="b83-aU-AJ3" secondAttribute="centerY" id="flh-LO-k3n"/>
<constraint firstItem="vBS-iO-8H6" firstAttribute="centerY" secondItem="b83-aU-AJ3" secondAttribute="centerY" id="ocY-mt-0n0"/>
<constraint firstAttribute="trailingMargin" relation="greaterThanOrEqual" secondItem="vBS-iO-8H6" secondAttribute="trailing" constant="15" id="qqy-zs-Ccy"/>
<constraint firstItem="vBS-iO-8H6" firstAttribute="leading" secondItem="HJd-Am-cCx" secondAttribute="trailing" constant="14" id="quv-47-R9n"/>
<constraint firstAttribute="trailing" secondItem="m0f-of-6xq" secondAttribute="trailing" constant="22" id="k1V-mv-zgW"/>
<constraint firstAttribute="bottomMargin" secondItem="vBS-iO-8H6" secondAttribute="bottom" constant="4" id="k1n-ZD-FS0"/>
<constraint firstItem="vBS-iO-8H6" firstAttribute="leading" secondItem="HJd-Am-cCx" secondAttribute="trailing" constant="12" id="quv-47-R9n"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -60,11 +69,13 @@
<outlet property="avatarImageView" destination="HJd-Am-cCx" id="Mv2-Kb-aG4"/>
<outlet property="encryptedRoomIcon" destination="hqD-YY-LWz" id="Ikp-Sf-Vlc"/>
<outlet property="roomTitleLabel" destination="vBS-iO-8H6" id="0J9-p3-Lzx"/>
<outlet property="selectionButton" destination="m0f-of-6xq" id="pzc-VN-t72"/>
</connections>
<point key="canvasLocation" x="-53" y="-43"/>
</tableViewCell>
</objects>
<resources>
<image name="e2e_verified" width="10" height="12"/>
<image name="radio-button-selected" width="24" height="24"/>
</resources>
</document>

View file

@ -18,8 +18,8 @@
#import "MXRoom+Riot.h"
#import "ShareDataSource.h"
@class RoomsListViewController;
@interface RoomsListViewController : MXKRecentListViewController
@property (copy) void (^failureBlock)(void);
@end

View file

@ -14,13 +14,13 @@
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
#import "RoomsListViewController.h"
#import "RecentRoomTableViewCell.h"
#import "NSBundle+MatrixKit.h"
#import "ShareExtensionManager.h"
#import "ShareDataSource.h"
#import "RecentCellData.h"
#import "ThemeService.h"
#import <MatrixKit/MatrixKit.h>
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
@ -28,9 +28,7 @@
#import "Riot-Swift.h"
#endif
@interface RoomsListViewController () <ShareExtensionManagerDelegate>
@property (nonatomic) MXKPieChartHUD *hudView;
@interface RoomsListViewController ()
// The fake search bar displayed at the top of the recents table. We switch on the actual search bar (self.recentsSearchBar)
// when the user selects it.
@ -136,76 +134,6 @@
return;
}
#pragma mark - Private
- (void)showShareAlertForRoomPath:(NSIndexPath *)indexPath
{
MXKRecentCellData *recentCellData = [self.dataSource cellDataAtIndexPath:indexPath];
NSString *roomName = recentCellData.roomSummary.displayname;
if (!roomName.length)
{
roomName = [MatrixKitL10n roomDisplaynameEmptyRoom];
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[VectorL10n sendTo:roomName] message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:cancelAction];
UIAlertAction *sendAction = [UIAlertAction actionWithTitle:[MatrixKitL10n send] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// The selected room is instanciated here
MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:[ShareExtensionManager sharedManager].userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]];
[MXFileStore setPreloadOptions:0];
MXWeakify(session);
[session setStore:[ShareExtensionManager sharedManager].fileStore success:^{
MXStrongifyAndReturnIfNil(session);
MXRoom *selectedRoom = [MXRoom loadRoomFromStore:[ShareExtensionManager sharedManager].fileStore withRoomId:recentCellData.roomSummary.roomId matrixSession:session];
// Do not warn for unknown devices. We have cross-signing now
session.crypto.warnOnUnknowDevices = NO;
[ShareExtensionManager sharedManager].delegate = self;
[[ShareExtensionManager sharedManager] sendContentToRoom:selectedRoom failureBlock:^(NSError* error) {
NSString *title;
if ([error.domain isEqualToString:MXEncryptingErrorDomain])
{
title = [VectorL10n shareExtensionFailedToEncrypt];
}
[self showFailureAlert:title];
}];
} failure:^(NSError *error) {
MXLogDebug(@"[RoomsListViewController] failed to prepare matrix session]");
}];
}];
[alertController addAction:sendAction];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showFailureAlert:(NSString *)title
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title.length ? title : [VectorL10n roomEventFailedToSend] message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (self.failureBlock)
{
self.failureBlock();
}
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
@ -217,7 +145,16 @@
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self showShareAlertForRoomPath:indexPath];
NSString *roomIdentifier = [self.dataSource cellDataAtIndexPath:indexPath].roomSummary.roomId;
ShareDataSource *dataSource = (ShareDataSource *)self.dataSource;
if ([dataSource.selectedRoomIdentifiers containsObject:roomIdentifier]) {
[dataSource deselectRoomWithIdentifier:roomIdentifier animated:YES];
} else {
[dataSource selectRoomWithIdentifier:roomIdentifier animated:YES];
}
[self.recentsTableView reloadData];
}
#pragma mark - MXKDataSourceDelegate
@ -304,34 +241,4 @@
}
}
#pragma mark - ShareExtensionManagerDelegate
- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager showImageCompressionPrompt:(UIAlertController *)compressionPrompt
{
dispatch_async(dispatch_get_main_queue(), ^{
[compressionPrompt popoverPresentationController].sourceView = self.view;
[compressionPrompt popoverPresentationController].sourceRect = self.view.frame;
[self presentViewController:compressionPrompt animated:YES completion:nil];
});
}
- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager didStartSendingContentToRoom:(MXRoom *)room
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.hudView)
{
self.parentViewController.view.userInteractionEnabled = NO;
self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:[VectorL10n sending]];
[self.hudView setProgress:0.0];
}
});
}
- (void)shareExtensionManager:(ShareExtensionManager *)extensionManager mediaUploadProgress:(CGFloat)progress
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.hudView setProgress:progress];
});
}
@end

View file

@ -0,0 +1,57 @@
/*
Copyright 2017 Aram Sargsyan
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@import UIKit;
@class ShareViewController;
@class ShareDataSource;
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, ShareViewControllerType) {
ShareViewControllerTypeSend,
ShareViewControllerTypeForward
};
typedef NS_ENUM(NSUInteger, ShareViewControllerAccountState) {
ShareViewControllerAccountStateConfigured,
ShareViewControllerAccountStateNotConfigured
};
@protocol ShareViewControllerDelegate <NSObject>
- (void)shareViewController:(ShareViewController *)shareViewController didRequestShareForRoomIdentifiers:(NSSet<NSString *> *)roomIdentifiers;
- (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController;
@end
@interface ShareViewController : UIViewController
@property (nonatomic, weak, nullable) id<ShareViewControllerDelegate> delegate;
- (instancetype)initWithType:(ShareViewControllerType)type
currentState:(ShareViewControllerAccountState)state;
- (void)configureWithState:(ShareViewControllerAccountState)state
roomDataSource:(nullable ShareDataSource *)roomDataSource;
- (void)showProgressIndicator;
- (void)setProgress:(CGFloat)progress;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,193 @@
/*
Copyright 2017 Aram Sargsyan
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 "ShareViewController.h"
#import "ShareDataSource.h"
#import "RoomsListViewController.h"
#import "FallbackViewController.h"
#import "ThemeService.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
@interface ShareViewController () <MXKRecentListViewControllerDelegate, ShareDataSourceDelegate>
@property (nonatomic, assign, readonly) ShareViewControllerType type;
@property (nonatomic, assign) ShareViewControllerAccountState state;
@property (nonatomic, strong) RoomsListViewController *roomListViewController;
@property (nonatomic, strong) ShareDataSource *roomDataSource;
@property (nonatomic, strong) FallbackViewController *fallbackViewController;
@property (nonatomic, weak) IBOutlet UIView *masterContainerView;
@property (nonatomic, weak) IBOutlet UIButton *cancelButton;
@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
@property (nonatomic, weak) IBOutlet UIButton *shareButton;
@property (nonatomic, weak) IBOutlet UIView *contentView;
@property (nonatomic, strong) MXKPieChartHUD *hudView;
@end
@implementation ShareViewController
- (instancetype)initWithType:(ShareViewControllerType)type
currentState:(ShareViewControllerAccountState)state
{
if (self = [super init])
{
_type = type;
_state = state;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.masterContainerView setBackgroundColor:ThemeService.shared.theme.baseColor];
[self.masterContainerView.layer setCornerRadius:7.0];
[self.titleLabel setTextColor:ThemeService.shared.theme.textPrimaryColor];
[self.cancelButton setTintColor:ThemeService.shared.theme.tintColor];
[self.cancelButton setTitle:[VectorL10n cancel] forState:UIControlStateNormal];
[self.shareButton setTintColor:ThemeService.shared.theme.tintColor];
[self.shareButton setEnabled:NO];
[self configureWithState:self.state roomDataSource:self.roomDataSource];
}
- (void)configureWithState:(ShareViewControllerAccountState)state
roomDataSource:(ShareDataSource *)roomDataSource
{
self.state = state;
self.roomDataSource = roomDataSource;
self.roomDataSource.shareDelegate = self;
if (!self.isViewLoaded) {
return;
}
[self configureViews];
}
- (void)showProgressIndicator
{
if (!self.hudView)
{
self.parentViewController.view.userInteractionEnabled = NO;
self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:[VectorL10n sending]];
[self.hudView setProgress:0.0];
}
}
- (void)setProgress:(CGFloat)progress
{
[self.hudView setProgress:progress];
}
#pragma mark - ShareDataSourceDelegate
- (void)shareDataSourceDidChangeSelectedRoomIdentifiers:(ShareDataSource *)shareDataSource
{
self.shareButton.enabled = (shareDataSource.selectedRoomIdentifiers.count > 0);
}
#pragma mark - Private
- (void)configureViews
{
[self resetContentView];
if (self.state == ShareViewControllerAccountStateConfigured)
{
[self configureSegmentedViewController];
[self.shareButton setHidden:NO];
if (self.type == ShareViewControllerTypeSend) {
[self.titleLabel setText:[VectorL10n sendTo:@""]];
[self.shareButton setTitle:[VectorL10n sendTo:@""] forState:UIControlStateNormal];
} else {
[self.titleLabel setText:[VectorL10n roomEventActionForward]];
[self.shareButton setTitle:[VectorL10n roomEventActionForward] forState:UIControlStateNormal];
}
}
else
{
[self configureFallbackViewController];
[self.shareButton setHidden:NO];
self.titleLabel.text = [AppInfo.current displayName];
}
}
- (void)configureSegmentedViewController
{
self.roomListViewController = [RoomsListViewController recentListViewController];
[self.roomListViewController displayList:self.roomDataSource];
[self addChildViewController:self.roomListViewController];
[self.contentView vc_addSubViewMatchingParent:self.roomListViewController.view];
[self.roomListViewController didMoveToParentViewController:self];
}
- (void)configureFallbackViewController
{
self.fallbackViewController = [FallbackViewController new];
[self addChildViewController:self.fallbackViewController];
[self.contentView vc_addSubViewMatchingParent:self.fallbackViewController.view];
[self.fallbackViewController didMoveToParentViewController:self];
}
- (void)resetContentView
{
[self.roomListViewController willMoveToParentViewController:nil];
[self.roomListViewController.view removeFromSuperview];
[self.roomListViewController removeFromParentViewController];
[self.fallbackViewController willMoveToParentViewController:nil];
[self.fallbackViewController.view removeFromSuperview];
[self.fallbackViewController removeFromParentViewController];
}
#pragma mark - Actions
- (IBAction)onCancelButtonTap:(UIButton *)sender
{
[self.delegate shareViewControllerDidRequestDismissal:self];
}
- (IBAction)onShareButtonTap:(UIButton *)sender
{
if (self.roomDataSource.selectedRoomIdentifiers.count == 0) {
return;
}
[self.delegate shareViewController:self didRequestShareForRoomIdentifiers:self.roomDataSource.selectedRoomIdentifiers];
}
@end

View file

@ -1,16 +1,18 @@
<?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="19162" 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="19144"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ShareViewController">
<connections>
<outlet property="cancelButton" destination="xhU-Rh-IgE" id="o8b-Gf-ahK"/>
<outlet property="contentView" destination="jAn-9F-DlU" id="NWV-TS-MGK"/>
<outlet property="masterContainerView" destination="oax-z3-vv0" id="KvN-B4-ZkF"/>
<outlet property="shareButton" destination="otu-Ag-Bwo" id="IeY-fj-5zI"/>
<outlet property="titleLabel" destination="BQ5-AW-rsV" id="qm6-T7-3LB"/>
<outlet property="view" destination="Bej-An-0PZ" id="sgO-5Q-y1c"/>
</connections>
@ -21,40 +23,51 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oax-z3-vv0" userLabel="Master Container View">
<rect key="frame" x="30" y="58.5" width="315" height="550"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Ye-Ni-Nsr" userLabel="Title Container View">
<rect key="frame" x="0.0" y="0.0" width="315" height="40"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="57"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BQ5-AW-rsV" userLabel="Title Label">
<rect key="frame" x="132.5" y="8" width="50" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<rect key="frame" x="166" y="18.5" width="43.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RBD-vS-rcr">
<rect key="frame" x="5" y="2" width="36" height="36"/>
<constraints>
<constraint firstAttribute="width" secondItem="RBD-vS-rcr" secondAttribute="height" multiplier="1:1" id="ELS-bA-rFR"/>
<constraint firstAttribute="width" constant="36" id="Pym-oR-KoO"/>
</constraints>
<state key="normal" image="cancel"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xhU-Rh-IgE">
<rect key="frame" x="16" y="12" width="53" height="33"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Cancel"/>
<connections>
<action selector="close:" destination="-1" eventType="touchUpInside" id="rko-d0-x9l"/>
<action selector="onCancelButtonTap:" destination="-1" eventType="touchUpInside" id="47D-fR-8PQ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="otu-Ag-Bwo">
<rect key="frame" x="315" y="12" width="44" height="33"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Share"/>
<connections>
<action selector="onShareButtonTap:" destination="-1" eventType="touchUpInside" id="DzX-aj-yPy"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="BQ5-AW-rsV" firstAttribute="centerY" secondItem="7Ye-Ni-Nsr" secondAttribute="centerY" id="1d9-JD-CVF"/>
<constraint firstItem="RBD-vS-rcr" firstAttribute="leading" secondItem="7Ye-Ni-Nsr" secondAttribute="leading" constant="5" id="Swk-mu-OcC"/>
<constraint firstItem="xhU-Rh-IgE" firstAttribute="leading" secondItem="7Ye-Ni-Nsr" secondAttribute="leading" constant="16" id="EjM-0b-dot"/>
<constraint firstAttribute="height" constant="57" id="JLA-0h-k0P"/>
<constraint firstAttribute="trailing" secondItem="otu-Ag-Bwo" secondAttribute="trailing" constant="16" id="Mth-c3-3Th"/>
<constraint firstItem="xhU-Rh-IgE" firstAttribute="top" secondItem="7Ye-Ni-Nsr" secondAttribute="top" constant="12" id="T8c-d6-B7A"/>
<constraint firstItem="otu-Ag-Bwo" firstAttribute="centerY" secondItem="BQ5-AW-rsV" secondAttribute="centerY" id="YwS-RW-o1i"/>
<constraint firstItem="BQ5-AW-rsV" firstAttribute="centerX" secondItem="7Ye-Ni-Nsr" secondAttribute="centerX" id="ZDz-XD-buD"/>
<constraint firstAttribute="height" constant="40" id="dFb-An-mc6"/>
<constraint firstItem="RBD-vS-rcr" firstAttribute="centerY" secondItem="7Ye-Ni-Nsr" secondAttribute="centerY" id="wUd-cN-mek"/>
<constraint firstItem="otu-Ag-Bwo" firstAttribute="top" secondItem="7Ye-Ni-Nsr" secondAttribute="top" constant="12" id="c8g-rh-ucO"/>
<constraint firstItem="xhU-Rh-IgE" firstAttribute="centerY" secondItem="BQ5-AW-rsV" secondAttribute="centerY" id="eW9-wn-0Lc"/>
<constraint firstAttribute="bottom" secondItem="otu-Ag-Bwo" secondAttribute="bottom" constant="12" id="l6n-lP-CUn"/>
<constraint firstAttribute="bottom" secondItem="xhU-Rh-IgE" secondAttribute="bottom" constant="12" id="tHm-CS-WsP"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jAn-9F-DlU">
<rect key="frame" x="0.0" y="40" width="315" height="510"/>
<rect key="frame" x="0.0" y="57" width="375" height="610"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
@ -64,9 +77,7 @@
<constraint firstItem="jAn-9F-DlU" firstAttribute="top" secondItem="7Ye-Ni-Nsr" secondAttribute="bottom" id="1zc-qn-eFO"/>
<constraint firstItem="7Ye-Ni-Nsr" firstAttribute="leading" secondItem="oax-z3-vv0" secondAttribute="leading" id="RTO-at-ZR0"/>
<constraint firstAttribute="trailing" secondItem="7Ye-Ni-Nsr" secondAttribute="trailing" id="RtW-o3-kOS"/>
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="550" id="Uon-Cb-HY2"/>
<constraint firstItem="7Ye-Ni-Nsr" firstAttribute="top" secondItem="oax-z3-vv0" secondAttribute="top" id="YIj-72-BeJ"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="550" id="de2-m2-xUY"/>
<constraint firstItem="jAn-9F-DlU" firstAttribute="leading" secondItem="oax-z3-vv0" secondAttribute="leading" id="hju-02-xMk"/>
<constraint firstAttribute="bottom" secondItem="jAn-9F-DlU" secondAttribute="bottom" id="rDT-Zr-eUB"/>
</constraints>
@ -74,21 +85,12 @@
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="oax-z3-vv0" secondAttribute="bottom" priority="750" constant="10" id="Qjd-aK-VSL"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="oax-z3-vv0" secondAttribute="trailing" constant="30" id="VUX-RZ-Jke"/>
<constraint firstAttribute="trailing" secondItem="oax-z3-vv0" secondAttribute="trailing" priority="750" constant="30" id="cYZ-zQ-1Dh"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Bej-An-0PZ" secondAttribute="leading" constant="30" id="h8V-gU-iRt"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="centerY" secondItem="Bej-An-0PZ" secondAttribute="centerY" id="lbe-HZ-ZsR"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="top" secondItem="Bej-An-0PZ" secondAttribute="top" priority="750" constant="40" id="loD-b9-Eog"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="centerX" secondItem="Bej-An-0PZ" secondAttribute="centerX" id="mzY-Ok-RAp"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="oax-z3-vv0" secondAttribute="bottom" constant="10" id="qnO-IP-fp3"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="leading" secondItem="Bej-An-0PZ" secondAttribute="leading" priority="750" constant="30" id="sYU-AK-85d"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="top" relation="greaterThanOrEqual" secondItem="Bej-An-0PZ" secondAttribute="top" constant="40" id="vXO-uW-nFN"/>
<constraint firstAttribute="trailing" secondItem="oax-z3-vv0" secondAttribute="trailing" id="VUX-RZ-Jke"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="leading" secondItem="Bej-An-0PZ" secondAttribute="leading" id="h8V-gU-iRt"/>
<constraint firstAttribute="bottom" secondItem="oax-z3-vv0" secondAttribute="bottom" id="qnO-IP-fp3"/>
<constraint firstItem="oax-z3-vv0" firstAttribute="top" secondItem="Bej-An-0PZ" secondAttribute="top" id="vXO-uW-nFN"/>
</constraints>
<point key="canvasLocation" x="39.5" y="89.5"/>
</view>
</objects>
<resources>
<image name="cancel" width="20" height="20"/>
</resources>
</document>

View file

@ -16,8 +16,6 @@
#import <UIKit/UIKit.h>
@interface SharePresentingViewController : UIViewController
- (void)destroy;
@interface ShareExtensionRootViewController : UIViewController
@end

View file

@ -0,0 +1,84 @@
/*
Copyright 2017 Aram Sargsyan
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 "ShareExtensionRootViewController.h"
#import "ShareManager.h"
#import "ThemeService.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
@interface ShareExtensionRootViewController ()
@property (nonatomic, strong, readonly) ShareManager *shareManager;
@end
@implementation ShareExtensionRootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[ThemeService.shared setThemeId:RiotSettings.shared.userInterfaceTheme];
ShareExtensionShareItemProvider *provider = [[ShareExtensionShareItemProvider alloc] initWithExtensionContext:self.extensionContext];
_shareManager = [[ShareManager alloc] initWithShareItemProvider:provider type:ShareManagerTypeSend];
MXWeakify(self);
[_shareManager setCompletionCallback:^(ShareManagerResult result) {
MXStrongifyAndReturnIfNil(self);
switch (result)
{
case ShareManagerResultFinished:
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
[self dismiss];
break;
case ShareManagerResultCancelled:
[self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]];
[self dismiss];
break;
case ShareManagerResultFailed:
[self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXFailureErrorDomain" code:500 userInfo:nil]];
[self dismiss];
break;
default:
break;
}
}];
[self.shareManager.mainViewController setModalInPopover:YES];
[self presentViewController:self.shareManager.mainViewController animated:YES completion:nil];
}
#pragma mark - Private
- (void)dismiss
{
[self dismissViewControllerAnimated:true completion:^{
[self.presentingViewController dismissViewControllerAnimated:false completion:nil];
// FIXME: Share extension memory usage increase when launched several times and then crash due to some memory leaks.
// For now, we force the share extension to exit and free memory.
[NSException raise:@"Kill the app extension" format:@"Free memory used by share extension"];
}];
}
@end

View file

@ -0,0 +1,130 @@
//
// Copyright 2021 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 MobileCoreServices
import MatrixKit
private class ShareExtensionItem: ShareItemProtocol {
let itemProvider: NSItemProvider
var loaded = false
init(itemProvider: NSItemProvider) {
self.itemProvider = itemProvider
}
var type: ShareItemType {
if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.text.rawValue) {
return .text
} else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.url.rawValue) {
return .URL
} else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.fileUrl.rawValue) {
return .fileURL
} else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.image.rawValue) {
return .image
} else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.video.rawValue) {
return .video
} else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.movie.rawValue) {
return .movie
}
return .unknown
}
}
@objcMembers
class ShareExtensionShareItemProvider: NSObject, ShareItemProviderProtocol {
public let items: [ShareItemProtocol]
public init(extensionContext: NSExtensionContext) {
var items: [ShareItemProtocol] = []
for case let extensionItem as NSExtensionItem in extensionContext.inputItems {
guard let attachments = extensionItem.attachments else {
continue;
}
for itemProvider in attachments {
items.append(ShareExtensionItem(itemProvider: itemProvider))
}
}
self.items = items
}
func areAllItemsLoaded() -> Bool {
for case let item as ShareExtensionItem in self.items {
if !item.loaded {
return false
}
}
return true
}
func areAllItemsImages() -> Bool {
for case let item as ShareExtensionItem in self.items {
if item.type != .image {
return false
}
}
return true
}
func loadItem(_ item: ShareItemProtocol, completion: @escaping (Any?, Error?) -> Void) {
guard let shareExtensionItem = item as? ShareExtensionItem else {
fatalError("[ShareExtensionShareItemProvider] Unexpected item type.")
}
let typeIdentifier = typeIdentifierForType(item.type)
shareExtensionItem.loaded = false
shareExtensionItem.itemProvider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { result, error in
if error == nil {
shareExtensionItem.loaded = true
}
DispatchQueue.main.async {
completion(result, error)
}
}
}
// MARK: - Private
private func typeIdentifierForType(_ type: ShareItemType) -> String {
switch type {
case .text:
return MXKUTI.text.rawValue
case .URL:
return MXKUTI.url.rawValue
case .fileURL:
return MXKUTI.fileUrl.rawValue
case .image:
return MXKUTI.image.rawValue
case .video:
return MXKUTI.video.rawValue
case .movie:
return MXKUTI.movie.rawValue
case .voiceMessage:
return MXKUTI.fileUrl.rawValue
default:
return ""
}
}
}

View file

@ -43,7 +43,7 @@
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>SharePresentingViewController</string>
<string>ShareExtensionRootViewController</string>
</dict>
</dict>
</plist>

View file

@ -4,3 +4,4 @@
#import "ThemeService.h"
#import "AvatarGenerator.h"
#import "BuildInfo.h"

View file

@ -52,9 +52,11 @@ targets:
- path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift
- path: ../Riot/Managers/KeyValueStorage
- path: ../Riot/Managers/Settings/RiotSettings.swift
- path: ../Riot/Managers/AppInfo/
- path: ../Riot/Categories/UIColor.swift
- path: ../Riot/Categories/UISearchBar.swift
- path: ../Riot/Categories/String.swift
- path: ../Riot/Categories/UIView.swift
- path: ../Riot/Modules/Common/Recents/CellData/RecentCellData.m
- path: ../Riot/PropertyWrappers/UserDefaultsBackedPropertyWrapper.swift
- path: ../Riot/Generated/Strings.swift

1
changelog.d/5009.feature Normal file
View file

@ -0,0 +1 @@
Implemented message forwarding from within the main application. Updated the share extension designs.