зеркало из https://github.com/nextcloud/talk-ios.git
Add inline audio player in voice messages.
Signed-off-by: Ivan Sein <ivan@nextcloud.com>
This commit is contained in:
Родитель
9c4d14a1d7
Коммит
189fec1fcb
|
@ -188,6 +188,7 @@
|
|||
2CA1CCDB1F1F6FCA002FE6A2 /* RoomTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CA1CCD91F1F6FCA002FE6A2 /* RoomTableViewCell.m */; };
|
||||
2CA52ACB2670D02800619610 /* VoiceMessageRecordingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CA52ACA2670D02800619610 /* VoiceMessageRecordingView.m */; };
|
||||
2CA52ACD2670D07900619610 /* VoiceMessageRecordingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CA52ACC2670D07900619610 /* VoiceMessageRecordingView.xib */; };
|
||||
2CA52AD0267613CB00619610 /* VoiceMessageTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CA52ACF267613CA00619610 /* VoiceMessageTableViewCell.m */; };
|
||||
2CB304192264775E0053078A /* SLKInputAccessoryView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CB3039D2264775E0053078A /* SLKInputAccessoryView.m */; };
|
||||
2CB3041A2264775E0053078A /* SLKTextInput+Implementation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CB3039E2264775E0053078A /* SLKTextInput+Implementation.m */; };
|
||||
2CB3041B2264775E0053078A /* SLKTextInputbar.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CB303A12264775E0053078A /* SLKTextInputbar.m */; };
|
||||
|
@ -610,6 +611,8 @@
|
|||
2CA52AC92670D02800619610 /* VoiceMessageRecordingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoiceMessageRecordingView.h; sourceTree = "<group>"; };
|
||||
2CA52ACA2670D02800619610 /* VoiceMessageRecordingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VoiceMessageRecordingView.m; sourceTree = "<group>"; };
|
||||
2CA52ACC2670D07900619610 /* VoiceMessageRecordingView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VoiceMessageRecordingView.xib; sourceTree = "<group>"; };
|
||||
2CA52ACE267613CA00619610 /* VoiceMessageTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoiceMessageTableViewCell.h; sourceTree = "<group>"; };
|
||||
2CA52ACF267613CA00619610 /* VoiceMessageTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VoiceMessageTableViewCell.m; sourceTree = "<group>"; };
|
||||
2CA80EDB256C1249006BA449 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
2CA80EDC256C1249006BA449 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
2CA80EDD256C1296006BA449 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -1102,6 +1105,8 @@
|
|||
2C604BD8211988A700D34DCD /* SystemMessageTableViewCell.m */,
|
||||
2C8E2A19232174C20022BFC9 /* MessageSeparatorTableViewCell.h */,
|
||||
2C8E2A1A232174C20022BFC9 /* MessageSeparatorTableViewCell.m */,
|
||||
2CA52ACE267613CA00619610 /* VoiceMessageTableViewCell.h */,
|
||||
2CA52ACF267613CA00619610 /* VoiceMessageTableViewCell.m */,
|
||||
);
|
||||
name = "Chat cells";
|
||||
sourceTree = "<group>";
|
||||
|
@ -1776,6 +1781,7 @@
|
|||
C1292B8C237313590004C3B7 /* CCBKPasscode.m in Sources */,
|
||||
2C1ABDE5257F883400AEDFB6 /* ABContact.m in Sources */,
|
||||
2CA1CCDB1F1F6FCA002FE6A2 /* RoomTableViewCell.m in Sources */,
|
||||
2CA52AD0267613CB00619610 /* VoiceMessageTableViewCell.m in Sources */,
|
||||
2C1EF36B25505DCE007C9768 /* NCNavigationController.m in Sources */,
|
||||
2CC007B420D7AE990096D91F /* ResultMultiSelectionTableViewController.m in Sources */,
|
||||
2CA1CCC31F166CC5002FE6A2 /* NCRoom.m in Sources */,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "baseline_pause_black_24pt_1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_pause_black_24pt_2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_pause_black_24pt_3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Двоичные данные
NextcloudTalk/Images.xcassets/pause.imageset/baseline_pause_black_24pt_1x.png
поставляемый
Normal file
Двоичные данные
NextcloudTalk/Images.xcassets/pause.imageset/baseline_pause_black_24pt_1x.png
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 83 B |
Двоичные данные
NextcloudTalk/Images.xcassets/pause.imageset/baseline_pause_black_24pt_2x.png
поставляемый
Normal file
Двоичные данные
NextcloudTalk/Images.xcassets/pause.imageset/baseline_pause_black_24pt_2x.png
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 103 B |
Двоичные данные
NextcloudTalk/Images.xcassets/pause.imageset/baseline_pause_black_24pt_3x.png
поставляемый
Normal file
Двоичные данные
NextcloudTalk/Images.xcassets/pause.imageset/baseline_pause_black_24pt_3x.png
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 109 B |
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "baseline_play_arrow_black_24pt_1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_play_arrow_black_24pt_2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "baseline_play_arrow_black_24pt_3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Двоичные данные
NextcloudTalk/Images.xcassets/play.imageset/baseline_play_arrow_black_24pt_1x.png
поставляемый
Normal file
Двоичные данные
NextcloudTalk/Images.xcassets/play.imageset/baseline_play_arrow_black_24pt_1x.png
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 144 B |
Двоичные данные
NextcloudTalk/Images.xcassets/play.imageset/baseline_play_arrow_black_24pt_2x.png
поставляемый
Normal file
Двоичные данные
NextcloudTalk/Images.xcassets/play.imageset/baseline_play_arrow_black_24pt_2x.png
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 212 B |
Двоичные данные
NextcloudTalk/Images.xcassets/play.imageset/baseline_play_arrow_black_24pt_3x.png
поставляемый
Normal file
Двоичные данные
NextcloudTalk/Images.xcassets/play.imageset/baseline_play_arrow_black_24pt_3x.png
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 265 B |
|
@ -42,6 +42,7 @@ extern NSString * const NCChatFileControllerDidChangeDownloadProgressNotificatio
|
|||
@interface NCChatFileController : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<NCChatFileControllerDelegate> delegate;
|
||||
@property (nonatomic, strong) NSString *messageType;
|
||||
|
||||
- (void)downloadFileFromMessage:(NCMessageFileParameter *)fileParameter;
|
||||
- (void)downloadFileWithFileId:(NSString *)fileId;
|
||||
|
|
|
@ -35,6 +35,7 @@ extern NSString * const kMessageTypeComment;
|
|||
extern NSString * const kMessageTypeCommentDeleted;
|
||||
extern NSString * const kMessageTypeSystem;
|
||||
extern NSString * const kMessageTypeCommand;
|
||||
extern NSString * const kMessageTypeVoiceMessage;
|
||||
|
||||
@interface NCChatMessage : RLMObject <NSCopying>
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ NSString * const kMessageTypeComment = @"comment";
|
|||
NSString * const kMessageTypeCommentDeleted = @"comment_deleted";
|
||||
NSString * const kMessageTypeSystem = @"system";
|
||||
NSString * const kMessageTypeCommand = @"command";
|
||||
NSString * const kMessageTypeVoiceMessage = @"voice-message";
|
||||
|
||||
@interface NCChatMessage ()
|
||||
{
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
#import "SystemMessageTableViewCell.h"
|
||||
#import "ShareLocationViewController.h"
|
||||
#import "VoiceMessageRecordingView.h"
|
||||
#import "VoiceMessageTableViewCell.h"
|
||||
|
||||
|
||||
#define k_send_message_button_tag 99
|
||||
|
@ -84,7 +85,7 @@ typedef enum NCChatMessageAction {
|
|||
kNCChatMessageActionOpenFileInNextcloud
|
||||
} NCChatMessageAction;
|
||||
|
||||
@interface NCChatViewController () <UIGestureRecognizerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIDocumentPickerDelegate, ShareConfirmationViewControllerDelegate, FileMessageTableViewCellDelegate, NCChatFileControllerDelegate, QLPreviewControllerDelegate, QLPreviewControllerDataSource, ChatMessageTableViewCellDelegate, ShareLocationViewControllerDelegate, LocationMessageTableViewCellDelegate, AVAudioRecorderDelegate>
|
||||
@interface NCChatViewController () <UIGestureRecognizerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIDocumentPickerDelegate, ShareConfirmationViewControllerDelegate, FileMessageTableViewCellDelegate, NCChatFileControllerDelegate, QLPreviewControllerDelegate, QLPreviewControllerDataSource, ChatMessageTableViewCellDelegate, ShareLocationViewControllerDelegate, LocationMessageTableViewCellDelegate, VoiceMessageTableViewCellDelegate, AVAudioRecorderDelegate, AVAudioPlayerDelegate>
|
||||
|
||||
@property (nonatomic, strong) NCChatController *chatController;
|
||||
@property (nonatomic, strong) NCChatTitleView *titleView;
|
||||
|
@ -125,6 +126,9 @@ typedef enum NCChatMessageAction {
|
|||
@property (nonatomic, assign) CGPoint longPressStartingPoint;
|
||||
@property (nonatomic, assign) CGFloat cancelHintLabelInitialPositionX;
|
||||
@property (nonatomic, assign) BOOL recordCancelled;
|
||||
@property (nonatomic, strong) AVAudioPlayer *voiceMessagesPlayer;
|
||||
@property (nonatomic, strong) NSTimer *playerProgressTimer;
|
||||
@property (nonatomic, strong) NCChatFileStatus *playerAudioFileStatus;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -292,6 +296,8 @@ NSString * const NCChatViewControllerReplyPrivatelyNotification = @"NCChatViewCo
|
|||
[self.tableView registerClass:[LocationMessageTableViewCell class] forCellReuseIdentifier:GroupedLocationMessageCellIdentifier];
|
||||
[self.tableView registerClass:[SystemMessageTableViewCell class] forCellReuseIdentifier:SystemMessageCellIdentifier];
|
||||
[self.tableView registerClass:[SystemMessageTableViewCell class] forCellReuseIdentifier:InvisibleSystemMessageCellIdentifier];
|
||||
[self.tableView registerClass:[VoiceMessageTableViewCell class] forCellReuseIdentifier:VoiceMessageCellIdentifier];
|
||||
[self.tableView registerClass:[VoiceMessageTableViewCell class] forCellReuseIdentifier:GroupedVoiceMessageCellIdentifier];
|
||||
[self.tableView registerClass:[MessageSeparatorTableViewCell class] forCellReuseIdentifier:MessageSeparatorCellIdentifier];
|
||||
[self.autoCompletionView registerClass:[ChatMessageTableViewCell class] forCellReuseIdentifier:AutoCompletionCellIdentifier];
|
||||
[self registerPrefixesForAutoCompletion:@[@"@"]];
|
||||
|
@ -1446,13 +1452,77 @@ NSString * const NCChatViewControllerReplyPrivatelyNotification = @"NCChatViewCo
|
|||
|
||||
#pragma mark - AVAudioRecorderDelegate
|
||||
|
||||
- (void) audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
|
||||
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
|
||||
{
|
||||
if (flag && recorder == _recorder && !_recordCancelled) {
|
||||
[self shareVoiceMessage];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Voice Messages Player
|
||||
|
||||
- (void)setupVoiceMessagePlayerWithAudioFileStatus:(NCChatFileStatus *)fileStatus
|
||||
{
|
||||
NSData *data = [NSData dataWithContentsOfFile:fileStatus.fileLocalPath];
|
||||
NSError *error;
|
||||
_voiceMessagesPlayer = [[AVAudioPlayer alloc] initWithData:data error:&error];
|
||||
if (!error) {
|
||||
_playerAudioFileStatus = fileStatus;
|
||||
[self playVoiceMessagePlayer];
|
||||
} else {
|
||||
NSLog(@"Error: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)playVoiceMessagePlayer
|
||||
{
|
||||
[self startVoiceMessagePlayerTimer];
|
||||
[_voiceMessagesPlayer play];
|
||||
}
|
||||
|
||||
- (void)pauseVoiceMessagePlayer
|
||||
{
|
||||
[self stopVoiceMessagePlayerTimer];
|
||||
[_voiceMessagesPlayer pause];
|
||||
[self checkVisibleCellAudioPlayers];
|
||||
}
|
||||
|
||||
- (void)checkVisibleCellAudioPlayers
|
||||
{
|
||||
for (NSIndexPath *indexPath in self.tableView.indexPathsForVisibleRows) {
|
||||
NSDate *sectionDate = [_dateSections objectAtIndex:indexPath.section];
|
||||
NCChatMessage *message = [[_messages objectForKey:sectionDate] objectAtIndex:indexPath.row];
|
||||
if ([message.messageType isEqualToString:kMessageTypeVoiceMessage]) {
|
||||
VoiceMessageTableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
if (message.file && [message.file.parameterId isEqualToString:_playerAudioFileStatus.fileId] && [message.file.path isEqualToString:_playerAudioFileStatus.filePath]) {
|
||||
[cell setPlayerProgress:_voiceMessagesPlayer.currentTime/_voiceMessagesPlayer.duration isPlaying:_voiceMessagesPlayer.isPlaying];
|
||||
continue;
|
||||
}
|
||||
[cell resetPlayer];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startVoiceMessagePlayerTimer
|
||||
{
|
||||
[self stopVoiceMessagePlayerTimer];
|
||||
_playerProgressTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(checkVisibleCellAudioPlayers) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
- (void)stopVoiceMessagePlayerTimer
|
||||
{
|
||||
[_playerProgressTimer invalidate];
|
||||
_playerProgressTimer = nil;
|
||||
}
|
||||
|
||||
#pragma mark - AVAudioPlayerDelegate
|
||||
|
||||
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
|
||||
{
|
||||
[self stopVoiceMessagePlayerTimer];
|
||||
[self checkVisibleCellAudioPlayers];
|
||||
}
|
||||
|
||||
#pragma mark - Gesture recognizer
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
|
@ -2579,6 +2649,14 @@ NSString * const NCChatViewControllerReplyPrivatelyNotification = @"NCChatViewCo
|
|||
return systemCell;
|
||||
}
|
||||
if (message.file) {
|
||||
if ([message.messageType isEqualToString:kMessageTypeVoiceMessage]) {
|
||||
NSString *voiceMessageCellIdentifier = (message.isGroupMessage) ? GroupedVoiceMessageCellIdentifier : VoiceMessageCellIdentifier;
|
||||
VoiceMessageTableViewCell *voiceMessageCell = (VoiceMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:voiceMessageCellIdentifier];
|
||||
voiceMessageCell.delegate = self;
|
||||
|
||||
[voiceMessageCell setupForMessage:message withLastCommonReadMessage:_room.lastCommonReadMessage];
|
||||
return voiceMessageCell;
|
||||
}
|
||||
NSString *fileCellIdentifier = (message.isGroupMessage) ? GroupedFileMessageCellIdentifier : FileMessageCellIdentifier;
|
||||
FileMessageTableViewCell *fileCell = (FileMessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:fileCellIdentifier];
|
||||
fileCell.delegate = self;
|
||||
|
@ -2684,12 +2762,18 @@ NSString * const NCChatViewControllerReplyPrivatelyNotification = @"NCChatViewCo
|
|||
}
|
||||
}
|
||||
|
||||
// Voice message should be before message.file check since it contains a file
|
||||
if ([message.messageType isEqualToString:kMessageTypeVoiceMessage]) {
|
||||
height -= CGRectGetHeight(bodyBounds);
|
||||
return height += kVoiceMessageCellPlayerHeight;
|
||||
}
|
||||
|
||||
if (message.file) {
|
||||
height += kFileMessageCellFilePreviewHeight + 15;
|
||||
return height += kFileMessageCellFilePreviewHeight + 15;
|
||||
}
|
||||
|
||||
if (message.geoLocation) {
|
||||
height += kLocationMessageCellPreviewHeight + 15;
|
||||
return height += kLocationMessageCellPreviewHeight + 15;
|
||||
}
|
||||
|
||||
return height;
|
||||
|
@ -2871,6 +2955,33 @@ NSString * const NCChatViewControllerReplyPrivatelyNotification = @"NCChatViewCo
|
|||
[downloader downloadFileFromMessage:fileParameter];
|
||||
}
|
||||
|
||||
#pragma mark - VoiceMessageTableViewCellDelegate
|
||||
|
||||
- (void)cellWantsToPlayAudioFile:(NCMessageFileParameter *)fileParameter
|
||||
{
|
||||
if (fileParameter.fileStatus && fileParameter.fileStatus.isDownloading) {
|
||||
NSLog(@"File already downloading -> skipping new download");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_voiceMessagesPlayer.isPlaying && [fileParameter.parameterId isEqualToString:_playerAudioFileStatus.fileId] && [fileParameter.path isEqualToString:_playerAudioFileStatus.filePath]) {
|
||||
[self playVoiceMessagePlayer];
|
||||
return;
|
||||
}
|
||||
|
||||
NCChatFileController *downloader = [[NCChatFileController alloc] init];
|
||||
downloader.delegate = self;
|
||||
downloader.messageType = kMessageTypeVoiceMessage;
|
||||
[downloader downloadFileFromMessage:fileParameter];
|
||||
}
|
||||
|
||||
- (void)cellWantsToPauseAudioFile:(NCMessageFileParameter *)fileParameter
|
||||
{
|
||||
if (_voiceMessagesPlayer.isPlaying && [fileParameter.parameterId isEqualToString:_playerAudioFileStatus.fileId] && [fileParameter.path isEqualToString:_playerAudioFileStatus.filePath]) {
|
||||
[self pauseVoiceMessagePlayer];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - LocationMessageTableViewCellDelegate
|
||||
|
||||
- (void)cellWantsToOpenLocation:(GeoLocationRichObject *)geoLocationRichObject
|
||||
|
@ -2897,6 +3008,11 @@ NSString * const NCChatViewControllerReplyPrivatelyNotification = @"NCChatViewCo
|
|||
|
||||
- (void)fileControllerDidLoadFile:(NCChatFileController *)fileController withFileStatus:(NCChatFileStatus *)fileStatus
|
||||
{
|
||||
if ([fileController.messageType isEqualToString:kMessageTypeVoiceMessage]) {
|
||||
[self setupVoiceMessagePlayerWithAudioFileStatus:fileStatus];
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isPreviewControllerShown) {
|
||||
// We are showing a file already, no need to open another one
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
||||
*
|
||||
* @author Ivan Sein <ivan@nextcloud.com>
|
||||
*
|
||||
* @license GNU GPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ChatTableViewCell.h"
|
||||
#import "MessageBodyTextView.h"
|
||||
#import "NCMessageFileParameter.h"
|
||||
#import "NCChatMessage.h"
|
||||
|
||||
static CGFloat kVoiceMessageCellMinimumHeight = 50.0;
|
||||
static CGFloat kVoiceMessageCellAvatarHeight = 30.0;
|
||||
static CGFloat kVoiceMessageCellPlayerHeight = 44.0;
|
||||
|
||||
static NSString *VoiceMessageCellIdentifier = @"VoiceMessageCellIdentifier";
|
||||
static NSString *GroupedVoiceMessageCellIdentifier = @"GroupedVoiceMessageCellIdentifier";
|
||||
|
||||
@protocol VoiceMessageTableViewCellDelegate <NSObject>
|
||||
|
||||
- (void)cellWantsToPlayAudioFile:(NCMessageFileParameter *)fileParameter;
|
||||
- (void)cellWantsToPauseAudioFile:(NCMessageFileParameter *)fileParameter;
|
||||
|
||||
@end
|
||||
|
||||
@interface VoiceMessageTableViewCell : ChatTableViewCell
|
||||
|
||||
@property (nonatomic, weak) id<VoiceMessageTableViewCellDelegate> delegate;
|
||||
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UILabel *dateLabel;
|
||||
@property (nonatomic, strong) MessageBodyTextView *bodyTextView;
|
||||
@property (nonatomic, strong) UIImageView *avatarView;
|
||||
@property (nonatomic, strong) UIView *statusView;
|
||||
@property (nonatomic, strong) UIView *fileStatusView;
|
||||
@property (nonatomic, strong) NCMessageFileParameter *fileParameter;
|
||||
@property (nonatomic, strong) UIButton *playPauseButton;
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
|
||||
+ (CGFloat)defaultFontSize;
|
||||
- (void)setGuestAvatar:(NSString *)displayName;
|
||||
- (void)setupForMessage:(NCChatMessage *)message withLastCommonReadMessage:(NSInteger)lastCommonRead;
|
||||
- (void)setPlayerProgress:(CGFloat)progress isPlaying:(BOOL)playing;
|
||||
- (void)resetPlayer;
|
||||
|
||||
@end
|
|
@ -0,0 +1,398 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
||||
*
|
||||
* @author Ivan Sein <ivan@nextcloud.com>
|
||||
*
|
||||
* @license GNU GPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "VoiceMessageTableViewCell.h"
|
||||
|
||||
#import "MaterialActivityIndicator.h"
|
||||
#import "SLKUIConstants.h"
|
||||
#import "UIImageView+AFNetworking.h"
|
||||
#import "UIImageView+Letters.h"
|
||||
|
||||
#import "NCAPIController.h"
|
||||
#import "NCAppBranding.h"
|
||||
#import "NCChatFileController.h"
|
||||
#import "NCDatabaseManager.h"
|
||||
#import "NCUtils.h"
|
||||
|
||||
#define k_play_button_tag 99
|
||||
#define k_pause_button_tag 98
|
||||
|
||||
@interface VoiceMessageTableViewCell ()
|
||||
{
|
||||
MDCActivityIndicator *_activityIndicator;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation VoiceMessageTableViewCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
|
||||
{
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.backgroundColor = [NCAppBranding backgroundColor];
|
||||
[self configureSubviews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configureSubviews
|
||||
{
|
||||
_avatarView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, kVoiceMessageCellAvatarHeight, kVoiceMessageCellAvatarHeight)];
|
||||
_avatarView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_avatarView.userInteractionEnabled = NO;
|
||||
_avatarView.backgroundColor = [NCAppBranding placeholderColor];
|
||||
_avatarView.layer.cornerRadius = kVoiceMessageCellAvatarHeight/2.0;
|
||||
_avatarView.layer.masksToBounds = YES;
|
||||
|
||||
if ([self.reuseIdentifier isEqualToString:VoiceMessageCellIdentifier]) {
|
||||
[self.contentView addSubview:_avatarView];
|
||||
[self.contentView addSubview:self.titleLabel];
|
||||
[self.contentView addSubview:self.dateLabel];
|
||||
}
|
||||
[self.contentView addSubview:self.bodyTextView];
|
||||
|
||||
self.playPauseButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.playPauseButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.playPauseButton addTarget:self action:@selector(playPauseButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self setPlayButton];
|
||||
[self.contentView addSubview:self.playPauseButton];
|
||||
|
||||
self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
|
||||
self.progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.contentView addSubview:self.progressView];
|
||||
|
||||
_statusView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kChatCellStatusViewHeight, kChatCellStatusViewHeight)];
|
||||
_statusView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
// [_statusView setBackgroundColor:[UIColor greenColor]];
|
||||
[self.contentView addSubview:_statusView];
|
||||
|
||||
_fileStatusView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kChatCellStatusViewHeight, kChatCellStatusViewHeight)];
|
||||
_fileStatusView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
// [_fileStatusView setBackgroundColor:[UIColor purpleColor]];
|
||||
[self.contentView addSubview:_fileStatusView];
|
||||
|
||||
NSDictionary *views = @{@"avatarView": self.avatarView,
|
||||
@"statusView": self.statusView,
|
||||
@"fileStatusView": self.fileStatusView,
|
||||
@"playButton" : self.playPauseButton,
|
||||
@"progressView" : self.progressView,
|
||||
@"titleLabel": self.titleLabel,
|
||||
@"dateLabel": self.dateLabel,
|
||||
@"bodyTextView": self.bodyTextView,
|
||||
};
|
||||
|
||||
NSDictionary *metrics = @{@"avatarSize": @(kVoiceMessageCellAvatarHeight),
|
||||
@"statusSize": @(kChatCellStatusViewHeight),
|
||||
@"statusTopPadding": @17,
|
||||
@"buttonHeight": @44,
|
||||
@"progressWidth": @200,
|
||||
@"progressTopPadding": @25,
|
||||
@"progressBottomPadding": @30,
|
||||
@"progressHeight": @4,
|
||||
@"padding": @15,
|
||||
@"avatarGap": @50,
|
||||
@"right": @10,
|
||||
@"left": @5
|
||||
};
|
||||
|
||||
if ([self.reuseIdentifier isEqualToString:VoiceMessageCellIdentifier]) {
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-right-[avatarView(avatarSize)]-right-[titleLabel]-[dateLabel(40)]-right-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-avatarGap-[playButton(buttonHeight)]-[progressView(progressWidth)]-[fileStatusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-padding-[statusView(statusSize)]-padding-[bodyTextView(>=0)]-right-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(28)]-left-[playButton(buttonHeight)]-right-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(28)]-progressTopPadding-[progressView(progressHeight)]-progressBottomPadding-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[dateLabel(28)]-left-[playButton(buttonHeight)]-right-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[avatarView(avatarSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(28)]-statusTopPadding-[fileStatusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[titleLabel(28)]-statusTopPadding-[statusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
} else if ([self.reuseIdentifier isEqualToString:GroupedVoiceMessageCellIdentifier]) {
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-avatarGap-[playButton(buttonHeight)]-[progressView(progressWidth)]-[fileStatusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-padding-[statusView(statusSize)]-padding-[bodyTextView(>=0)]-right-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-left-[playButton(buttonHeight)]-right-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-progressTopPadding-[progressView(progressHeight)]-progressBottomPadding-[bodyTextView(>=0@999)]-left-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-statusTopPadding-[fileStatusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-statusTopPadding-[statusView(statusSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeIsDownloading:) name:NCChatFileControllerDidChangeIsDownloadingNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeDownloadProgress:) name:NCChatFileControllerDidChangeDownloadProgressNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
CGFloat pointSize = [VoiceMessageTableViewCell defaultFontSize];
|
||||
|
||||
self.titleLabel.font = [UIFont systemFontOfSize:pointSize];
|
||||
self.bodyTextView.font = [UIFont systemFontOfSize:pointSize];
|
||||
|
||||
self.titleLabel.text = @"";
|
||||
self.bodyTextView.text = @"";
|
||||
self.dateLabel.text = @"";
|
||||
|
||||
[self.avatarView cancelImageDownloadTask];
|
||||
self.avatarView.image = nil;
|
||||
|
||||
[self.statusView.subviews makeObjectsPerformSelector: @selector(removeFromSuperview)];
|
||||
[self clearFileStatusView];
|
||||
}
|
||||
|
||||
- (void)setupForMessage:(NCChatMessage *)message withLastCommonReadMessage:(NSInteger)lastCommonRead
|
||||
{
|
||||
self.titleLabel.text = message.actorDisplayName;
|
||||
self.messageId = message.messageId;
|
||||
|
||||
NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:message.timestamp];
|
||||
self.dateLabel.text = [NCUtils getTimeFromDate:date];
|
||||
|
||||
TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
|
||||
[self.avatarView setImageWithURLRequest:[[NCAPIController sharedInstance] createAvatarRequestForUser:message.actorId andSize:96 usingAccount:activeAccount]
|
||||
placeholderImage:nil success:nil failure:nil];
|
||||
|
||||
if (message.sendingFailed) {
|
||||
UIImageView *errorView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
|
||||
[errorView setImage:[UIImage imageNamed:@"error"]];
|
||||
[self.statusView addSubview:errorView];
|
||||
} else if (message.isTemporary) {
|
||||
[self addActivityIndicator:0];
|
||||
} else if (message.file.fileStatus) {
|
||||
if (message.file.fileStatus.isDownloading && message.file.fileStatus.downloadProgress < 1) {
|
||||
[self addActivityIndicator:message.file.fileStatus.downloadProgress];
|
||||
}
|
||||
}
|
||||
|
||||
self.fileParameter = message.file;
|
||||
|
||||
ServerCapabilities *serverCapabilities = [[NCDatabaseManager sharedInstance] serverCapabilitiesForAccountId:activeAccount.accountId];
|
||||
BOOL shouldShowDeliveryStatus = [[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityChatReadStatus forAccountId:activeAccount.accountId];
|
||||
BOOL shouldShowReadStatus = !serverCapabilities.readStatusPrivacy;
|
||||
if ([message.actorId isEqualToString:activeAccount.userId] && [message.actorType isEqualToString:@"users"] && shouldShowDeliveryStatus) {
|
||||
if (lastCommonRead >= message.messageId && shouldShowReadStatus) {
|
||||
[self setDeliveryState:ChatMessageDeliveryStateRead];
|
||||
} else {
|
||||
[self setDeliveryState:ChatMessageDeliveryStateSent];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDeliveryState:(ChatMessageDeliveryState)state
|
||||
{
|
||||
[self.statusView.subviews makeObjectsPerformSelector: @selector(removeFromSuperview)];
|
||||
|
||||
if (state == ChatMessageDeliveryStateSent) {
|
||||
UIImageView *checkView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
|
||||
[checkView setImage:[UIImage imageNamed:@"check"]];
|
||||
checkView.image = [checkView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[checkView setTintColor:[UIColor lightGrayColor]];
|
||||
[self.statusView addSubview:checkView];
|
||||
} else if (state == ChatMessageDeliveryStateRead) {
|
||||
UIImageView *checkAllView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
|
||||
[checkAllView setImage:[UIImage imageNamed:@"check-all"]];
|
||||
checkAllView.image = [checkAllView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[checkAllView setTintColor:[UIColor lightGrayColor]];
|
||||
[self.statusView addSubview:checkAllView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPlayerProgress:(CGFloat)progress isPlaying:(BOOL)playing
|
||||
{
|
||||
[self setPauseButton];
|
||||
if (!playing) {
|
||||
[self setPlayButton];
|
||||
}
|
||||
[self.progressView setProgress:progress];
|
||||
}
|
||||
- (void)resetPlayer
|
||||
{
|
||||
[self setPlayButton];
|
||||
[self.progressView setProgress:0];
|
||||
}
|
||||
|
||||
- (void)setPlayButton
|
||||
{
|
||||
UIImage *image = [[UIImage imageNamed:@"play"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[self.playPauseButton setImage:image forState:UIControlStateNormal];
|
||||
self.playPauseButton.tag = k_play_button_tag;
|
||||
}
|
||||
|
||||
- (void)setPauseButton
|
||||
{
|
||||
UIImage *image = [[UIImage imageNamed:@"pause"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[self.playPauseButton setImage:image forState:UIControlStateNormal];
|
||||
self.playPauseButton.tag = k_pause_button_tag;
|
||||
}
|
||||
|
||||
- (void)didChangeIsDownloading:(NSNotification *)notification
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NCChatFileStatus *receivedStatus = [notification.userInfo objectForKey:@"fileStatus"];
|
||||
|
||||
if (![receivedStatus.fileId isEqualToString:self->_fileParameter.parameterId] || ![receivedStatus.filePath isEqualToString:self->_fileParameter.path]) {
|
||||
// Received a notification for a different cell
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isDownloading = [[notification.userInfo objectForKey:@"isDownloading"] boolValue];
|
||||
|
||||
if (isDownloading && !self->_activityIndicator) {
|
||||
// Immediately show an indeterminate indicator as long as we don't have a progress value
|
||||
[self addActivityIndicator:0];
|
||||
} else if (!isDownloading && self->_activityIndicator) {
|
||||
[self clearFileStatusView];
|
||||
}
|
||||
});
|
||||
}
|
||||
- (void)didChangeDownloadProgress:(NSNotification *)notification
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NCChatFileStatus *receivedStatus = [notification.userInfo objectForKey:@"fileStatus"];
|
||||
|
||||
if (![receivedStatus.fileId isEqualToString:self->_fileParameter.parameterId] || ![receivedStatus.filePath isEqualToString:self->_fileParameter.path]) {
|
||||
// Received a notification for a different cell
|
||||
return;
|
||||
}
|
||||
|
||||
double progress = [[notification.userInfo objectForKey:@"progress"] doubleValue];
|
||||
|
||||
if (self->_activityIndicator) {
|
||||
// Switch to determinate-mode and show progress
|
||||
self->_activityIndicator.indicatorMode = MDCActivityIndicatorModeDeterminate;
|
||||
[self->_activityIndicator setProgress:progress animated:YES];
|
||||
} else {
|
||||
// Make sure we have an activity indicator added to this cell
|
||||
[self addActivityIndicator:progress];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addActivityIndicator:(CGFloat)progress
|
||||
{
|
||||
[self clearFileStatusView];
|
||||
|
||||
_activityIndicator = [[MDCActivityIndicator alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
|
||||
_activityIndicator.radius = 7.0f;
|
||||
_activityIndicator.cycleColors = @[UIColor.lightGrayColor];
|
||||
|
||||
if (progress > 0) {
|
||||
_activityIndicator.indicatorMode = MDCActivityIndicatorModeDeterminate;
|
||||
[_activityIndicator setProgress:progress animated:NO];
|
||||
}
|
||||
|
||||
[_activityIndicator startAnimating];
|
||||
[self.fileStatusView addSubview:_activityIndicator];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (UILabel *)titleLabel
|
||||
{
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [UILabel new];
|
||||
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_titleLabel.backgroundColor = [UIColor clearColor];
|
||||
_titleLabel.userInteractionEnabled = NO;
|
||||
_titleLabel.numberOfLines = 0;
|
||||
_titleLabel.textColor = [UIColor lightGrayColor];
|
||||
_titleLabel.font = [UIFont systemFontOfSize:[VoiceMessageTableViewCell defaultFontSize]];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_titleLabel.textColor = [UIColor secondaryLabelColor];
|
||||
}
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)dateLabel
|
||||
{
|
||||
if (!_dateLabel) {
|
||||
_dateLabel = [UILabel new];
|
||||
_dateLabel.textAlignment = NSTextAlignmentRight;
|
||||
_dateLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_dateLabel.backgroundColor = [UIColor clearColor];
|
||||
_dateLabel.userInteractionEnabled = NO;
|
||||
_dateLabel.numberOfLines = 0;
|
||||
_dateLabel.textColor = [UIColor lightGrayColor];
|
||||
_dateLabel.font = [UIFont systemFontOfSize:12.0];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_dateLabel.textColor = [UIColor secondaryLabelColor];
|
||||
}
|
||||
}
|
||||
return _dateLabel;
|
||||
}
|
||||
|
||||
- (MessageBodyTextView *)bodyTextView
|
||||
{
|
||||
if (!_bodyTextView) {
|
||||
_bodyTextView = [MessageBodyTextView new];
|
||||
_bodyTextView.font = [UIFont systemFontOfSize:[VoiceMessageTableViewCell defaultFontSize]];
|
||||
_bodyTextView.dataDetectorTypes = UIDataDetectorTypeNone;
|
||||
}
|
||||
return _bodyTextView;
|
||||
}
|
||||
|
||||
- (void)playPauseButtonTapped:(id)sender
|
||||
{
|
||||
if (!self.fileParameter || !self.fileParameter.path || !self.fileParameter.link) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.delegate) {
|
||||
UIButton *buttton = sender;
|
||||
if (buttton.tag == k_play_button_tag) {
|
||||
[self.delegate cellWantsToPlayAudioFile:self.fileParameter];
|
||||
} else if (buttton.tag == k_pause_button_tag) {
|
||||
[self.delegate cellWantsToPauseAudioFile:self.fileParameter];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setGuestAvatar:(NSString *)displayName
|
||||
{
|
||||
UIColor *guestAvatarColor = [NCAppBranding placeholderColor];
|
||||
NSString *name = ([displayName isEqualToString:@""]) ? @"?" : displayName;
|
||||
[_avatarView setImageWithString:name color:guestAvatarColor circular:true];
|
||||
}
|
||||
|
||||
- (void)clearFileStatusView {
|
||||
if (_activityIndicator) {
|
||||
[_activityIndicator stopAnimating];
|
||||
_activityIndicator = nil;
|
||||
}
|
||||
|
||||
[self.fileStatusView.subviews makeObjectsPerformSelector: @selector(removeFromSuperview)];
|
||||
}
|
||||
|
||||
+ (CGFloat)defaultFontSize
|
||||
{
|
||||
CGFloat pointSize = 16.0;
|
||||
|
||||
// NSString *contentSizeCategory = [[UIApplication sharedApplication] preferredContentSizeCategory];
|
||||
// pointSize += SLKPointSizeDifferenceForCategory(contentSizeCategory);
|
||||
|
||||
return pointSize;
|
||||
}
|
||||
|
||||
@end
|
Загрузка…
Ссылка в новой задаче