mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge pull request #5014 from vector-im/stefan/messageForwarding
Message forwarding
This commit is contained in:
commit
6f7a9145e4
45 changed files with 2015 additions and 1842 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json
vendored
Normal file
23
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png
vendored
Normal file
BIN
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 615 B |
BIN
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png
vendored
Normal file
BIN
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@3x.png
vendored
Normal file
BIN
Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
23
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json
vendored
Normal file
23
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png
vendored
Normal file
BIN
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 729 B |
BIN
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png
vendored
Normal file
BIN
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@3x.png
vendored
Normal file
BIN
Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 extension’s 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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
46
RiotShareExtension/Shared/ShareManager.h
Normal file
46
RiotShareExtension/Shared/ShareManager.h
Normal 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
|
1094
RiotShareExtension/Shared/ShareManager.m
Normal file
1094
RiotShareExtension/Shared/ShareManager.m
Normal file
File diff suppressed because it is too large
Load diff
|
@ -14,8 +14,8 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <MatrixKit/MatrixKit.h>
|
||||
@import UIKit;
|
||||
|
||||
@interface FallbackViewController : MXKViewController
|
||||
@interface FallbackViewController : UIViewController
|
||||
|
||||
@end
|
|
@ -42,10 +42,4 @@
|
|||
self.logoImageView.tintColor = ThemeService.shared.theme.tintColor;
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
@end
|
|
@ -20,4 +20,6 @@
|
|||
|
||||
+ (CGFloat)cellHeight;
|
||||
|
||||
- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated;
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -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>
|
|
@ -18,8 +18,8 @@
|
|||
#import "MXRoom+Riot.h"
|
||||
#import "ShareDataSource.h"
|
||||
|
||||
@class RoomsListViewController;
|
||||
|
||||
@interface RoomsListViewController : MXKRecentListViewController
|
||||
|
||||
@property (copy) void (^failureBlock)(void);
|
||||
|
||||
@end
|
|
@ -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
|
57
RiotShareExtension/Shared/View/ShareViewController.h
Normal file
57
RiotShareExtension/Shared/View/ShareViewController.h
Normal 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
|
193
RiotShareExtension/Shared/View/ShareViewController.m
Normal file
193
RiotShareExtension/Shared/View/ShareViewController.m
Normal 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
|
|
@ -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>
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SharePresentingViewController : UIViewController
|
||||
|
||||
- (void)destroy;
|
||||
@interface ShareExtensionRootViewController : UIViewController
|
||||
|
||||
@end
|
|
@ -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
|
130
RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift
Normal file
130
RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift
Normal 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 ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
#import "ThemeService.h"
|
||||
#import "AvatarGenerator.h"
|
||||
#import "BuildInfo.h"
|
||||
|
|
|
@ -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
1
changelog.d/5009.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Implemented message forwarding from within the main application. Updated the share extension designs.
|
Loading…
Reference in a new issue