Merge commit 'ea83772c8dfee5e7fe7450952ac35f3457bd12ab'

This commit is contained in:
Ali 2020-12-26 15:51:23 +04:00
commit 2cf2f3fc96
29 changed files with 592 additions and 100 deletions

View File

@ -5668,6 +5668,8 @@ Sorry for the inconvenience.";
"Media.LimitedAccessTitle" = "Limited Access to Media"; "Media.LimitedAccessTitle" = "Limited Access to Media";
"Media.LimitedAccessText" = "You've given Telegram access only to select number of photos."; "Media.LimitedAccessText" = "You've given Telegram access only to select number of photos.";
"Media.LimitedAccessManage" = "Manage"; "Media.LimitedAccessManage" = "Manage";
"Media.LimitedAccessSelectMore" = "Select More Photos...";
"Media.LimitedAccessChangeSettings" = "Change Settings";
"VoiceChat.StatusSpeaking" = "speaking"; "VoiceChat.StatusSpeaking" = "speaking";
"VoiceChat.StatusListening" = "listening"; "VoiceChat.StatusListening" = "listening";

View File

@ -466,6 +466,38 @@ public final class ContactSelectionControllerParams {
} }
} }
public enum ChatListSearchFilter: Equatable {
case chats
case media
case links
case files
case music
case voice
case peer(PeerId, Bool, String, String)
case date(Int32?, Int32, String)
public var id: Int32 {
switch self {
case .chats:
return 0
case .media:
return 1
case .links:
return 2
case .files:
return 3
case .music:
return 4
case .voice:
return 5
case let .peer(peerId, _, _, _):
return peerId.id
case let .date(_, date, _):
return date
}
}
}
#if ENABLE_WALLET #if ENABLE_WALLET
public enum OpenWalletContext { public enum OpenWalletContext {
case generic case generic
@ -526,6 +558,7 @@ public protocol SharedAccountContext: class {
func updateNotificationTokensRegistration() func updateNotificationTokensRegistration()
func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable
func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem)
func openSearch(filter: ChatListSearchFilter, query: String?)
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
func openChatMessage(_ params: OpenChatMessageParams) -> Bool func openChatMessage(_ params: OpenChatMessageParams) -> Bool
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>

View File

@ -8,7 +8,7 @@ public protocol ChatListController: ViewController {
var lockViewFrame: CGRect? { get } var lockViewFrame: CGRect? { get }
var isSearchActive: Bool { get } var isSearchActive: Bool { get }
func activateSearch() func activateSearch(filter: ChatListSearchFilter, query: String?)
func deactivateSearch(animated: Bool) func deactivateSearch(animated: Bool)
func activateCompose() func activateCompose()
func maybeAskForPeerChatRemoval(peer: RenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) func maybeAskForPeerChatRemoval(peer: RenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)

View File

@ -1679,7 +1679,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
public private(set) var isSearchActive: Bool = false public private(set) var isSearchActive: Bool = false
public func activateSearch() { public func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil) {
if self.displayNavigationBar { if self.displayNavigationBar {
let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(tagSummary: nil, actionsSummary: nil)) |> take(1)) let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(tagSummary: nil, actionsSummary: nil)) |> take(1))
|> deliverOnMainQueue).start(next: { [weak self] _, chatListView in |> deliverOnMainQueue).start(next: { [weak self] _, chatListView in
@ -1703,7 +1703,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
if let searchContentNode = strongSelf.searchContentNode { if let searchContentNode = strongSelf.searchContentNode {
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, navigationController: strongSelf.navigationController as? NavigationController) { if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) {
let (filterContainerNode, activate) = filterContainerNodeAndActivate let (filterContainerNode, activate) = filterContainerNodeAndActivate
if displaySearchFilters { if displaySearchFilters {
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false) strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
@ -1714,6 +1714,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
activate() activate()
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
searchContentNode.search(filter: filter, query: query)
}
if !tabsIsEmpty { if !tabsIsEmpty {
Queue.mainQueue().after(0.01) { Queue.mainQueue().after(0.01) {
filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 38.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 38.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
@ -1739,6 +1743,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
} }
} }
} else if self.isSearchActive {
if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
searchContentNode.search(filter: filter, query: query)
}
} }
} }

View File

@ -1146,7 +1146,7 @@ final class ChatListControllerNode: ASDisplayNode {
} }
} }
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? { func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return nil return nil
} }
@ -1156,7 +1156,7 @@ final class ChatListControllerNode: ASDisplayNode {
filter.insert(.excludeRecent) filter.insert(.excludeRecent)
} }
let contentNode = ChatListSearchContainerNode(context: self.context, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, openPeer: { [weak self] peer, dismissSearch in let contentNode = ChatListSearchContainerNode(context: self.context, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, initialFilter: initialFilter, openPeer: { [weak self] peer, dismissSearch in
self?.requestOpenPeerFromSearch?(peer, dismissSearch) self?.requestOpenPeerFromSearch?(peer, dismissSearch)
}, openDisabledPeer: { _ in }, openDisabledPeer: { _ in
}, openRecentPeerOptions: { [weak self] peer in }, openRecentPeerOptions: { [weak self] peer in

View File

@ -109,8 +109,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var stateValue = ChatListSearchContainerNodeSearchState() private var stateValue = ChatListSearchContainerNodeSearchState()
private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>() private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>()
private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id) private var selectedFilterKey: ChatListSearchFilterEntryId?
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>(.filter(ChatListSearchFilter.chats.id)) private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>()
private var transitionFraction: CGFloat = 0.0 private var transitionFraction: CGFloat = 0.0
private var didSetReady: Bool = false private var didSetReady: Bool = false
@ -121,7 +121,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, displaySearchFilters: Bool, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) { public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
self.context = context self.context = context
self.peersFilter = filter self.peersFilter = filter
self.groupId = groupId self.groupId = groupId
@ -129,6 +129,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.navigationController = navigationController self.navigationController = navigationController
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.selectedFilterKey = .filter(initialFilter.id)
self.selectedFilterKeyPromise.set(.single(self.selectedFilterKey))
self.openMessage = originalOpenMessage self.openMessage = originalOpenMessage
self.present = present self.present = present
self.presentInGlobalOverlay = presentInGlobalOverlay self.presentInGlobalOverlay = presentInGlobalOverlay
@ -293,6 +296,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
} }
self.filterContainerNode.filterPressed?(initialFilter)
let suggestedPeers = self.searchQuery.get() let suggestedPeers = self.searchQuery.get()
|> mapToSignal { query -> Signal<[Peer], NoError> in |> mapToSignal { query -> Signal<[Peer], NoError> in
if let query = query { if let query = query {
@ -489,6 +494,28 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.suggestedDates.set(.single(suggestDates(for: text, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat))) self.suggestedDates.set(.single(suggestDates(for: text, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)))
} }
public func search(filter: ChatListSearchFilter, query: String?) {
let key: ChatListSearchPaneKey
switch filter {
case .media:
key = .media
case .links:
key = .links
case .files:
key = .files
case .music:
key = .music
case .voice:
key = .voice
default:
key = .chats
}
self.paneContainerNode.requestSelectPane(key)
self.updateSearchOptions(nil)
self.searchTextUpdated(text: query ?? "")
self.setQuery?(nil, [], query ?? "")
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)

View File

@ -6,38 +6,7 @@ import SyncCore
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import AccountContext
enum ChatListSearchFilter: Equatable {
case chats
case media
case links
case files
case music
case voice
case peer(PeerId, Bool, String, String)
case date(Int32?, Int32, String)
var id: Int32 {
switch self {
case .chats:
return 0
case .media:
return 1
case .links:
return 2
case .files:
return 3
case .music:
return 4
case .voice:
return 5
case let .peer(peerId, _, _, _):
return peerId.id
case let .date(_, date, _):
return date
}
}
}
private final class ItemNode: ASDisplayNode { private final class ItemNode: ASDisplayNode {
private let pressed: () -> Void private let pressed: () -> Void

View File

@ -1730,6 +1730,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
nextRate = .x2 nextRate = .x2
case .x2: case .x2:
nextRate = .x1 nextRate = .x1
default:
nextRate = .x1
} }
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
return settings.withUpdatedVoicePlaybackRate(nextRate) return settings.withUpdatedVoicePlaybackRate(nextRate)

View File

@ -316,7 +316,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
self.currentPaneKey = nil self.currentPaneKey = nil
self.pendingSwitchToPaneKey = nil self.pendingSwitchToPaneKey = nil
} }
} else if self.currentPaneKey == nil { } else if self.currentPaneKey == nil && self.pendingSwitchToPaneKey == nil {
self.pendingSwitchToPaneKey = availablePanes.first self.pendingSwitchToPaneKey = availablePanes.first
} }

View File

@ -46,6 +46,7 @@ objc_library(
], ],
weak_sdk_frameworks = [ weak_sdk_frameworks = [
"Vision", "Vision",
"PhotosUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -32,6 +32,7 @@ typedef enum
@property (nonatomic, readonly) UIColor *textColor; @property (nonatomic, readonly) UIColor *textColor;
@property (nonatomic, readonly) UIColor *secondaryTextColor; @property (nonatomic, readonly) UIColor *secondaryTextColor;
@property (nonatomic, readonly) UIColor *accentColor; @property (nonatomic, readonly) UIColor *accentColor;
@property (nonatomic, readonly) UIColor *destructiveColor;
@property (nonatomic, readonly) UIColor *barBackgroundColor; @property (nonatomic, readonly) UIColor *barBackgroundColor;
@property (nonatomic, readonly) UIColor *barSeparatorColor; @property (nonatomic, readonly) UIColor *barSeparatorColor;
@property (nonatomic, readonly) UIColor *navigationTitleColor; @property (nonatomic, readonly) UIColor *navigationTitleColor;
@ -42,7 +43,7 @@ typedef enum
@property (nonatomic, readonly) UIColor *maybeAccentColor; @property (nonatomic, readonly) UIColor *maybeAccentColor;
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor; + (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor;
@end @end

View File

@ -36,6 +36,8 @@
@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); @property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t));
@property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t));
@property (nonatomic, assign) CGFloat topInset;
@property (nonatomic, strong) TGMediaAssetsPallete *pallete; @property (nonatomic, strong) TGMediaAssetsPallete *pallete;
@property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext;

View File

@ -1,5 +1,7 @@
#import "TGMediaAssetsPickerController.h" #import "TGMediaAssetsPickerController.h"
#import <Photos/Photos.h>
#import <PhotosUI/PhotosUI.h>
#import "LegacyComponentsInternal.h" #import "LegacyComponentsInternal.h"
#import "TGMediaGroupsController.h" #import "TGMediaGroupsController.h"
@ -26,14 +28,145 @@
#import <LegacyComponents/TGVideoEditAdjustments.h> #import <LegacyComponents/TGVideoEditAdjustments.h>
#import <LegacyComponents/TGPaintingData.h> #import <LegacyComponents/TGPaintingData.h>
#import "TGModernButton.h"
#import "PGPhotoEditor.h" #import "PGPhotoEditor.h"
@interface TGMediaPickerAccessView: UIView
{
TGMediaAssetsPallete *_pallete;
UIView *_backgroundView;
UIView *_separatorView;
UIView *_bottomSeparatorView;
UIImageView *_iconView;
UILabel *_labelView;
UILabel *_titleView;
UILabel *_textView;
TGModernButton * _buttonView;
CGSize _titleSize;
CGSize _textSize;
}
@property (nonatomic, assign) UIEdgeInsets safeAreaInset;
@property (nonatomic, copy) void (^pressed)(void);
@end
@implementation TGMediaPickerAccessView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
_backgroundView = [[UIView alloc] init];
_separatorView = [[UIView alloc] init];
_bottomSeparatorView = [[UIView alloc] init];
_iconView = [[UIImageView alloc] init];
_labelView = [[UILabel alloc] init];
_titleView = [[UILabel alloc] init];
_textView = [[UILabel alloc] init];
_labelView.font = TGSystemFontOfSize(14.0);
_labelView.text = @"!";
_labelView.textAlignment = NSTextAlignmentCenter;
_titleView.font = TGSemiboldSystemFontOfSize(17.0);
_titleView.text = TGLocalized(@"Media.LimitedAccessTitle");
_titleView.numberOfLines = 1;
_textView.font = TGSystemFontOfSize(14.0);
_textView.text = TGLocalized(@"Media.LimitedAccessText");
_textView.numberOfLines = 3;
_buttonView = [[TGModernButton alloc] init];
_buttonView.adjustsImageWhenHighlighted = false;
_buttonView.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
_buttonView.contentEdgeInsets = UIEdgeInsetsMake(0.0, 15.0, 0.0, 0.0);
_buttonView.titleLabel.font = TGSystemFontOfSize(17.0f);
_buttonView.highlightBackgroundColor = UIColorRGB(0xebebeb);
[_buttonView setTitle:TGLocalized(@"Media.LimitedAccessManage") forState:UIControlStateNormal];
[_buttonView addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_backgroundView];
[self addSubview:_separatorView];
[self addSubview:_bottomSeparatorView];
[self addSubview:_iconView];
[self addSubview:_labelView];
[self addSubview:_titleView];
[self addSubview:_textView];
[self addSubview:_buttonView];
}
return self;
}
- (void)buttonPressed {
self.pressed();
}
- (void)setPallete:(TGMediaAssetsPallete *)pallete {
_pallete = pallete;
_backgroundView.backgroundColor = pallete.backgroundColor;
_separatorView.backgroundColor = pallete.separatorColor;
_bottomSeparatorView.backgroundColor = pallete.separatorColor;
_titleView.textColor = pallete.textColor;
_textView.textColor = pallete.textColor;
_iconView.image = TGCircleImage(20.0, pallete.destructiveColor);
_labelView.textColor = pallete.badgeTextColor;
_buttonView.highlightBackgroundColor = pallete.selectionColor;
[_buttonView setTitleColor:pallete.accentColor];
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize result = CGSizeMake(size.width, 0.0);
CGSize constrainedSize = CGSizeMake(size.width - 30.0, size.height);
CGSize titleSize = [_titleView sizeThatFits:constrainedSize];
CGSize textSize = [_textView sizeThatFits:constrainedSize];
result.height += titleSize.height;
result.height += textSize.height;
result.height += 45.0;
result.height += 39.0;
result.height = CGFloor(result.height);
_titleSize = titleSize;
_textSize = textSize;
return result;
}
- (void)setSafeAreaInset:(UIEdgeInsets)safeAreaInset {
_safeAreaInset = safeAreaInset;
[self layoutSubviews];
}
- (void)layoutSubviews {
_backgroundView.frame = self.bounds;
_iconView.frame = CGRectMake(self.safeAreaInset.left + 15.0, 16.0, 20.0, 20.0);
_labelView.frame = _iconView.frame;
_buttonView.contentEdgeInsets = UIEdgeInsetsMake(0.0, self.safeAreaInset.left + 15.0, 0.0, 0.0);
_titleView.frame = CGRectMake(self.safeAreaInset.left + 42.0, 15.0, _titleSize.width, _titleSize.height);
_textView.frame = CGRectMake(self.safeAreaInset.left + 15.0, 46.0, _textSize.width, _textSize.height);
_separatorView.frame = CGRectMake(self.safeAreaInset.left + 15.0, self.bounds.size.height - 46.0, self.bounds.size.width, TGScreenPixel);
_buttonView.frame = CGRectMake(0.0, self.bounds.size.height - 46.0, self.bounds.size.width, 46.0);
_bottomSeparatorView.frame = CGRectMake(0.0, self.bounds.size.height - TGScreenPixel, self.bounds.size.width, TGScreenPixel);
}
@end
@interface TGMediaAssetsController () <UINavigationControllerDelegate, ASWatcher> @interface TGMediaAssetsController () <UINavigationControllerDelegate, ASWatcher>
{ {
TGMediaAssetsControllerIntent _intent; TGMediaAssetsControllerIntent _intent;
TGMediaPickerToolbarView *_toolbarView; TGMediaPickerToolbarView *_toolbarView;
TGMediaPickerAccessView *_accessView;
SMetaDisposable *_groupingChangedDisposable; SMetaDisposable *_groupingChangedDisposable;
SMetaDisposable *_selectionChangedDisposable; SMetaDisposable *_selectionChangedDisposable;
SMetaDisposable *_timersChangedDisposable; SMetaDisposable *_timersChangedDisposable;
@ -456,6 +589,8 @@
_toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; _toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
if ((_intent != TGMediaAssetsControllerSendFileIntent && _intent != TGMediaAssetsControllerSendMediaIntent && _intent != TGMediaAssetsControllerPassportMultipleIntent) || _selectionContext == nil) if ((_intent != TGMediaAssetsControllerSendFileIntent && _intent != TGMediaAssetsControllerSendMediaIntent && _intent != TGMediaAssetsControllerPassportMultipleIntent) || _selectionContext == nil)
[_toolbarView setRightButtonHidden:true]; [_toolbarView setRightButtonHidden:true];
__weak TGMediaAssetsController *weakSelf = self;
if (_selectionContext.allowGrouping) if (_selectionContext.allowGrouping)
{ {
[_toolbarView setCenterButtonImage:TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete != nil ? _pallete.secondaryTextColor : UIColorRGB(0x858e99))]; [_toolbarView setCenterButtonImage:TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete != nil ? _pallete.secondaryTextColor : UIColorRGB(0x858e99))];
@ -463,7 +598,6 @@
[_toolbarView setCenterButtonHidden:true animated:false]; [_toolbarView setCenterButtonHidden:true animated:false];
[_toolbarView setCenterButtonSelected:_selectionContext.grouping]; [_toolbarView setCenterButtonSelected:_selectionContext.grouping];
__weak TGMediaAssetsController *weakSelf = self;
_toolbarView.centerPressed = ^ _toolbarView.centerPressed = ^
{ {
__strong TGMediaAssetsController *strongSelf = weakSelf; __strong TGMediaAssetsController *strongSelf = weakSelf;
@ -472,6 +606,73 @@
}; };
} }
[self.view addSubview:_toolbarView]; [self.view addSubview:_toolbarView];
if (iosMajorVersion() >= 14 && [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite] == PHAuthorizationStatusLimited) {
_accessView = [[TGMediaPickerAccessView alloc] init];
_accessView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:self.interfaceOrientation hasOnScreenNavigation:hasOnScreenNavigation];
[_accessView setPallete:_pallete];
_accessView.pressed = ^{
__strong TGMediaAssetsController *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf manageAccess];
}
};
[self.view addSubview:_accessView];
}
}
- (void)manageAccess
{
if (iosMajorVersion() < 14) {
return;
}
__weak TGMediaAssetsController *weakSelf = self;
TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
controller.dismissesByOutsideTap = true;
controller.narrowInLandscape = true;
__weak TGMenuSheetController *weakController = controller;
NSArray *items = @
[
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessSelectMore") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
{
__strong TGMenuSheetController *strongController = weakController;
if (strongController == nil)
return;
__strong TGMediaAssetsController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongController dismissAnimated:true manual:false completion:nil];
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:strongSelf];
}],
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessChangeSettings") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
{
__strong TGMenuSheetController *strongController = weakController;
if (strongController == nil)
return;
__strong TGMediaAssetsController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongController dismissAnimated:true manual:false completion:nil];
[[[LegacyComponentsGlobals provider] applicationInstance] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}],
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
{
__strong TGMenuSheetController *strongController = weakController;
if (strongController != nil)
[strongController dismissAnimated:true];
}]
];
[controller setItemViews:items];
[controller presentInViewController:self sourceView:self.view animated:true];
} }
- (void)viewDidLoad - (void)viewDidLoad
@ -509,6 +710,12 @@
[_editingContext clearPaintingData]; [_editingContext clearPaintingData];
} }
- (void)setPallete:(TGMediaAssetsPallete *)pallete {
_pallete = pallete;
[_accessView setPallete:pallete];
}
- (void)viewDidLayoutSubviews - (void)viewDidLayoutSubviews
{ {
[super viewDidLayoutSubviews]; [super viewDidLayoutSubviews];
@ -522,7 +729,20 @@
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON; hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
_toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation]; _toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
_accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
if (_accessView != nil) {
CGSize accessSize = [_accessView sizeThatFits:self.view.frame.size];
_accessView.frame = CGRectMake(0.0, self.navigationBar.frame.size.height, self.view.frame.size.width, accessSize.height);
for (UIViewController *controller in self.viewControllers) {
if ([controller isKindOfClass:[TGMediaGroupsController class]]) {
((TGMediaGroupsController *)controller).topInset = accessSize.height;
} else if ([controller isKindOfClass:[TGMediaPickerController class]]) {
((TGMediaPickerController *)controller).topInset = accessSize.height;
}
}
}
if (_searchController == nil) if (_searchController == nil)
return; return;
@ -1384,7 +1604,7 @@
@implementation TGMediaAssetsPallete @implementation TGMediaAssetsPallete
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor + (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor
{ {
TGMediaAssetsPallete *pallete = [[TGMediaAssetsPallete alloc] init]; TGMediaAssetsPallete *pallete = [[TGMediaAssetsPallete alloc] init];
pallete->_isDark = dark; pallete->_isDark = dark;
@ -1394,6 +1614,7 @@
pallete->_textColor = textColor; pallete->_textColor = textColor;
pallete->_secondaryTextColor = secondaryTextColor; pallete->_secondaryTextColor = secondaryTextColor;
pallete->_accentColor = accentColor; pallete->_accentColor = accentColor;
pallete->_destructiveColor = destructiveColor;
pallete->_barBackgroundColor = barBackgroundColor; pallete->_barBackgroundColor = barBackgroundColor;
pallete->_barSeparatorColor = barSeparatorColor; pallete->_barSeparatorColor = barSeparatorColor;
pallete->_navigationTitleColor = navigationTitleColor; pallete->_navigationTitleColor = navigationTitleColor;

View File

@ -8,6 +8,7 @@
@property (nonatomic, assign) bool localMediaCacheEnabled; @property (nonatomic, assign) bool localMediaCacheEnabled;
@property (nonatomic, assign) bool liveVideoUploadEnabled; @property (nonatomic, assign) bool liveVideoUploadEnabled;
@property (nonatomic, assign) bool captionsEnabled; @property (nonatomic, assign) bool captionsEnabled;
@property (nonatomic, assign) CGFloat topInset;
@property (nonatomic, strong) TGMediaAssetsPallete *pallete; @property (nonatomic, strong) TGMediaAssetsPallete *pallete;

View File

@ -53,7 +53,6 @@
if (iosMajorVersion() >= 11) if (iosMajorVersion() >= 11)
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
_tableView.alwaysBounceVertical = true; _tableView.alwaysBounceVertical = true;
_tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_tableView.backgroundColor = self.view.backgroundColor; _tableView.backgroundColor = self.view.backgroundColor;
_tableView.delaysContentTouches = true; _tableView.delaysContentTouches = true;
_tableView.canCancelContentTouches = true; _tableView.canCancelContentTouches = true;
@ -71,6 +70,17 @@
[self controllerInsetUpdated:UIEdgeInsetsZero]; [self controllerInsetUpdated:UIEdgeInsetsZero];
} }
- (void)setTopInset:(CGFloat)topInset {
_topInset = topInset;
[self viewDidLayoutSubviews];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_tableView.frame = CGRectMake(0.0, _topInset, self.view.bounds.size.width, self.view.bounds.size.height - _topInset);
}
- (void)loadViewIfNeeded - (void)loadViewIfNeeded
{ {
if (iosMajorVersion() >= 9) if (iosMajorVersion() >= 9)

View File

@ -110,7 +110,7 @@
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation]; UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
CGSize frameSize = self.view.frame.size; CGSize frameSize = self.view.frame.size;
CGRect collectionViewFrame = CGRectMake(safeAreaInset.left, 0.0f, frameSize.width - safeAreaInset.left - safeAreaInset.right, frameSize.height); CGRect collectionViewFrame = CGRectMake(safeAreaInset.left, _topInset, frameSize.width - safeAreaInset.left - safeAreaInset.right, frameSize.height - _topInset);
_collectionViewWidth = collectionViewFrame.size.width; _collectionViewWidth = collectionViewFrame.size.width;
_collectionView.frame = collectionViewFrame; _collectionView.frame = collectionViewFrame;
} }
@ -280,6 +280,11 @@
[_collectionView setContentOffset:contentOffset animated:false]; [_collectionView setContentOffset:contentOffset animated:false];
} }
- (void)setTopInset:(CGFloat)topInset {
_topInset = topInset;
[self layoutControllerForSize:self.view.frame.size duration:0.0];
}
- (void)layoutControllerForSize:(CGSize)size duration:(NSTimeInterval)duration - (void)layoutControllerForSize:(CGSize)size duration:(NSTimeInterval)duration
{ {
[super layoutControllerForSize:size duration:duration]; [super layoutControllerForSize:size duration:duration];
@ -308,7 +313,7 @@
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation]; UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
CGRect frame = CGRectMake(safeAreaInset.left, 0, size.width - safeAreaInset.left - safeAreaInset.right, size.height); CGRect frame = CGRectMake(safeAreaInset.left, _topInset, size.width - safeAreaInset.left - safeAreaInset.right, size.height - _topInset);
_collectionViewWidth = frame.size.width; _collectionViewWidth = frame.size.width;
_collectionView.frame = frame; _collectionView.frame = frame;

View File

@ -288,7 +288,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone
let navigationBar = presentationTheme.rootController.navigationBar let navigationBar = presentationTheme.rootController.navigationBar
let tabBar = presentationTheme.rootController.tabBar let tabBar = presentationTheme.rootController.tabBar
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, barBackgroundColor: tabBar.backgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor) return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, destructiveColor: theme.itemDestructiveColor, barBackgroundColor: tabBar.backgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
} }
func checkButtonPallete() -> TGCheckButtonPallete! { func checkButtonPallete() -> TGCheckButtonPallete! {

View File

@ -189,6 +189,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
default:
break
} }
} }
} }
@ -383,6 +385,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: []) self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
case .x2: case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
default:
break
} }
} }
if let (size, leftInset, rightInset) = self.validLayout { if let (size, leftInset, rightInset) = self.validLayout {

View File

@ -663,6 +663,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
nextRate = .x2 nextRate = .x2
case .x2: case .x2:
nextRate = .x1 nextRate = .x1
default:
nextRate = .x1
} }
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
return settings.withUpdatedVoicePlaybackRate(nextRate) return settings.withUpdatedVoicePlaybackRate(nextRate)
@ -813,7 +815,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
})] })]
} }
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo) { open func joinGroupCall(peerId: PeerId, info: GroupCallInfo) {
self.context.joinGroupCall(peerId: peerId, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash)) self.context.joinGroupCall(peerId: peerId, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash))
} }
} }

View File

@ -736,9 +736,15 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
self.maskBlobView.startAnimating() self.maskBlobView.startAnimating()
self.maskBlobView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak self] _ in self.maskBlobView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak self] _ in
self?.maskBlobView.isHidden = true guard let strongSelf = self else {
self?.maskBlobView.stopAnimating() return
self?.maskBlobView.layer.removeAllAnimations() }
if strongSelf.state != .connecting && strongSelf.state != .disabled {
return
}
strongSelf.maskBlobView.isHidden = true
strongSelf.maskBlobView.stopAnimating()
strongSelf.maskBlobView.layer.removeAllAnimations()
}) })
CATransaction.begin() CATransaction.begin()
@ -750,6 +756,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
growthAnimation.isRemovedOnCompletion = false growthAnimation.isRemovedOnCompletion = false
CATransaction.setCompletionBlock { CATransaction.setCompletionBlock {
self.animatingDisappearance = false
if self.state != .connecting && self.state != .disabled {
return
}
CATransaction.begin() CATransaction.begin()
CATransaction.setDisableActions(true) CATransaction.setDisableActions(true)
self.disableGlowAnimations = false self.disableGlowAnimations = false
@ -757,7 +767,6 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
self.maskCircleLayer.isHidden = true self.maskCircleLayer.isHidden = true
self.growingForegroundCircleLayer.isHidden = true self.growingForegroundCircleLayer.isHidden = true
self.growingForegroundCircleLayer.removeAllAnimations() self.growingForegroundCircleLayer.removeAllAnimations()
self.animatingDisappearance = false
CATransaction.commit() CATransaction.commit()
} }
@ -790,11 +799,13 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
CATransaction.setCompletionBlock { CATransaction.setCompletionBlock {
CATransaction.begin() if case .blob = self.state {
CATransaction.setDisableActions(true) CATransaction.begin()
self.disableGlowAnimations = false CATransaction.setDisableActions(true)
self.foregroundCircleLayer.isHidden = true self.disableGlowAnimations = false
CATransaction.commit() self.foregroundCircleLayer.isHidden = true
CATransaction.commit()
}
} }
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink") self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
@ -829,40 +840,44 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
groupAnimation.duration = duration groupAnimation.duration = duration
CATransaction.setCompletionBlock { CATransaction.setCompletionBlock {
CATransaction.begin() if case .blob = self.state {
CATransaction.setDisableActions(true)
self.foregroundCircleLayer.isHidden = false
self.maskCircleLayer.isHidden = false
self.maskProgressLayer.isHidden = true
self.maskGradientLayer.isHidden = false
CATransaction.commit()
completion()
self.updateGlowAndGradientAnimations(active: active, previousActive: nil)
self.maskBlobView.isHidden = false
self.maskBlobView.startAnimating()
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
self.updatedActive?(true)
CATransaction.begin()
let shrinkAnimation = CABasicAnimation(keyPath: "transform.scale")
shrinkAnimation.fromValue = 1.0
shrinkAnimation.toValue = 0.0
shrinkAnimation.duration = 0.15
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
CATransaction.setCompletionBlock {
CATransaction.begin() CATransaction.begin()
CATransaction.setDisableActions(true) CATransaction.setDisableActions(true)
self.foregroundCircleLayer.isHidden = true self.foregroundCircleLayer.isHidden = false
self.maskCircleLayer.isHidden = false
self.maskProgressLayer.isHidden = true
self.maskGradientLayer.isHidden = false
CATransaction.commit()
completion()
self.updateGlowAndGradientAnimations(active: active, previousActive: nil)
self.maskBlobView.isHidden = false
self.maskBlobView.startAnimating()
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
self.updatedActive?(true)
CATransaction.begin()
let shrinkAnimation = CABasicAnimation(keyPath: "transform.scale")
shrinkAnimation.fromValue = 1.0
shrinkAnimation.toValue = 0.0
shrinkAnimation.duration = 0.15
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
CATransaction.setCompletionBlock {
if case .blob = self.state {
CATransaction.begin()
CATransaction.setDisableActions(true)
self.foregroundCircleLayer.isHidden = true
CATransaction.commit()
}
}
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
CATransaction.commit() CATransaction.commit()
} }
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
CATransaction.commit()
} }
self.maskProgressLayer.add(groupAnimation, forKey: "progressCompletion") self.maskProgressLayer.add(groupAnimation, forKey: "progressCompletion")

View File

@ -3,8 +3,11 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
private let circleDiameter: CGFloat = 80.0
final class IconButtonNode: HighlightTrackingButtonNode { final class IconButtonNode: HighlightTrackingButtonNode {
private let iconNode: ASImageNode private let iconNode: ASImageNode
private var circleNode: ASImageNode?
var icon: UIImage? { var icon: UIImage? {
didSet { didSet {
@ -14,12 +17,44 @@ final class IconButtonNode: HighlightTrackingButtonNode {
} }
} }
var circleColor: UIColor? {
didSet {
if let color = self.circleColor {
let circleNode: ASImageNode
if let current = self.circleNode {
circleNode = current
} else {
circleNode = ASImageNode()
circleNode.alpha = 0.0
circleNode.displaysAsynchronously = false
circleNode.displayWithoutProcessing = true
circleNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: circleDiameter, height: circleDiameter))
circleNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)
self.insertSubnode(circleNode, at: 0)
self.circleNode = circleNode
}
circleNode.image = generateFilledCircleImage(diameter: circleDiameter, color: color)
} else if let current = self.circleNode {
self.circleNode = nil
current.removeFromSupernode()
}
}
}
override var isEnabled: Bool { override var isEnabled: Bool {
didSet { didSet {
self.alpha = self.isEnabled ? 1.0 : 0.4 self.alpha = self.isEnabled ? 1.0 : 0.4
} }
} }
var isPressing = false {
didSet {
if self.isPressing != oldValue && !self.isPressing {
self.highligthedChanged(false)
}
}
}
init() { init() {
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true self.iconNode.isLayerBacked = true
@ -33,11 +68,17 @@ final class IconButtonNode: HighlightTrackingButtonNode {
self.highligthedChanged = { [weak self] highlighted in self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
if highlighted { if highlighted {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.09, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .linear)
transition.updateSublayerTransformScale(node: strongSelf, scale: 0.8) transition.updateSublayerTransformScale(node: strongSelf, scale: 0.8)
} else { if let circleNode = strongSelf.circleNode {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .spring) transition.updateAlpha(node: circleNode, alpha: 1.0)
}
} else if !strongSelf.isPressing {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .linear)
transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0) transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0)
if let circleNode = strongSelf.circleNode {
transition.updateAlpha(node: circleNode, alpha: 0.0)
}
} }
} }
} }
@ -51,5 +92,6 @@ final class IconButtonNode: HighlightTrackingButtonNode {
if let image = self.iconNode.image { if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
} }
self.circleNode?.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
} }
} }

View File

@ -73,6 +73,11 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
} }
}) })
} }
}, requestSearchByArtist: { [weak self] artist in
if let strongSelf = self {
strongSelf.context.sharedContext.openSearch(filter: .music, query: artist)
strongSelf.dismiss()
}
}) })
self.ready.set(self.controllerNode.ready.get()) self.ready.set(self.controllerNode.ready.get())

View File

@ -20,6 +20,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
private let type: MediaManagerPlayerType private let type: MediaManagerPlayerType
private let requestDismiss: () -> Void private let requestDismiss: () -> Void
private let requestShare: (MessageId) -> Void private let requestShare: (MessageId) -> Void
private let requestSearchByArtist: (String) -> Void
private let playlistLocation: SharedMediaPlaylistLocation? private let playlistLocation: SharedMediaPlaylistLocation?
private let isGlobalSearch: Bool private let isGlobalSearch: Bool
@ -42,13 +43,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private let replacementHistoryNodeReadyDisposable = MetaDisposable() private let replacementHistoryNodeReadyDisposable = MetaDisposable()
init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void) { init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void, requestSearchByArtist: @escaping (String) -> Void) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.type = type self.type = type
self.requestDismiss = requestDismiss self.requestDismiss = requestDismiss
self.requestShare = requestShare self.requestShare = requestShare
self.requestSearchByArtist = requestSearchByArtist
self.playlistLocation = playlistLocation self.playlistLocation = playlistLocation
if case .regular = initialOrder { if case .regular = initialOrder {
@ -224,6 +226,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
self?.requestShare(messageId) self?.requestShare(messageId)
} }
self.controlsNode.requestSearchByArtist = { [weak self] artist in
self?.requestSearchByArtist(artist)
}
self.controlsNode.updateOrder = { [weak self] order in self.controlsNode.updateOrder = { [weak self] order in
if let strongSelf = self { if let strongSelf = self {
let reversed: Bool let reversed: Bool

View File

@ -57,12 +57,13 @@ private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
return size.width return size.width
} }
private let titleFont = Font.semibold(17.0) private let titleFont = Font.semibold(18.0)
private let descriptionFont = Font.regular(17.0) private let descriptionFont = Font.regular(18.0)
private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, presentationData: PresentationData) -> (NSAttributedString?, NSAttributedString?) { private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, presentationData: PresentationData) -> (NSAttributedString?, NSAttributedString?, Bool) {
var titleString: NSAttributedString? var titleString: NSAttributedString?
var descriptionString: NSAttributedString? var descriptionString: NSAttributedString?
var hasArtist = false
if let data = data { if let data = data {
let titleText: String let titleText: String
@ -71,6 +72,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres
case let .music(title, performer, _, _): case let .music(title, performer, _, _):
titleText = title ?? presentationData.strings.MediaPlayer_UnknownTrack titleText = title ?? presentationData.strings.MediaPlayer_UnknownTrack
subtitleText = performer ?? presentationData.strings.MediaPlayer_UnknownArtist subtitleText = performer ?? presentationData.strings.MediaPlayer_UnknownArtist
hasArtist = performer != nil
case .voice, .instantVideo: case .voice, .instantVideo:
titleText = "" titleText = ""
subtitleText = "" subtitleText = ""
@ -80,7 +82,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres
descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: presentationData.theme.list.itemSecondaryTextColor) descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: presentationData.theme.list.itemSecondaryTextColor)
} }
return (titleString, descriptionString) return (titleString, descriptionString, hasArtist)
} }
final class OverlayPlayerControlsNode: ASDisplayNode { final class OverlayPlayerControlsNode: ASDisplayNode {
@ -97,6 +99,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private let titleNode: TextNode private let titleNode: TextNode
private let descriptionNode: TextNode private let descriptionNode: TextNode
private let shareNode: HighlightableButtonNode private let shareNode: HighlightableButtonNode
private let artistButton: HighlightTrackingButtonNode
private let scrubberNode: MediaPlayerScrubbingNode private let scrubberNode: MediaPlayerScrubbingNode
private let leftDurationLabel: MediaPlayerTimeTextNode private let leftDurationLabel: MediaPlayerTimeTextNode
@ -105,6 +108,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private let backwardButton: IconButtonNode private let backwardButton: IconButtonNode
private let forwardButton: IconButtonNode private let forwardButton: IconButtonNode
private var seekTimer: SwiftSignalKit.Timer?
private var seekRate: AudioPlaybackRate = .x2
private var previousRate: AudioPlaybackRate?
private var currentIsPaused: Bool? private var currentIsPaused: Bool?
private let playPauseButton: IconButtonNode private let playPauseButton: IconButtonNode
@ -124,6 +131,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
var requestCollapse: (() -> Void)? var requestCollapse: (() -> Void)?
var requestShare: ((MessageId) -> Void)? var requestShare: ((MessageId) -> Void)?
var requestSearchByArtist: ((String) -> Void)?
var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)? var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)?
var control: ((SharedMediaPlayerControlAction) -> Void)? var control: ((SharedMediaPlayerControlAction) -> Void)?
@ -167,6 +175,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.descriptionNode.isUserInteractionEnabled = false self.descriptionNode.isUserInteractionEnabled = false
self.descriptionNode.displaysAsynchronously = false self.descriptionNode.displaysAsynchronously = false
self.artistButton = HighlightTrackingButtonNode()
self.shareNode = HighlightableButtonNode() self.shareNode = HighlightableButtonNode()
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: []) self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
@ -215,6 +225,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.addSubnode(self.albumArtNode) self.addSubnode(self.albumArtNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.descriptionNode) self.addSubnode(self.descriptionNode)
self.addSubnode(self.artistButton)
self.addSubnode(self.shareNode) self.addSubnode(self.shareNode)
self.addSubnode(self.leftDurationLabel) self.addSubnode(self.leftDurationLabel)
@ -400,6 +411,23 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside) self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside)
self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), forControlEvents: .touchUpInside) self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), forControlEvents: .touchUpInside)
self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside) self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside)
self.artistButton.addTarget(self, action: #selector(self.artistPressed), forControlEvents: .touchUpInside)
self.artistButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.descriptionNode.layer.removeAnimation(forKey: "opacity")
strongSelf.descriptionNode.alpha = 0.4
} else {
strongSelf.descriptionNode.alpha = 1.0
strongSelf.descriptionNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.playPauseButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.backwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.forwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
} }
deinit { deinit {
@ -411,6 +439,80 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
super.didLoad() super.didLoad()
self.albumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:)))) self.albumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:))))
let backwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekForwardLongPress(_:)))
backwardLongPressGestureRecognizer.minimumPressDuration = 0.3
self.backwardButton.view.addGestureRecognizer(backwardLongPressGestureRecognizer)
let forwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekForwardLongPress(_:)))
forwardLongPressGestureRecognizer.minimumPressDuration = 0.3
self.forwardButton.view.addGestureRecognizer(forwardLongPressGestureRecognizer)
}
@objc private func seekBackwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
self.backwardButton.isPressing = true
self.previousRate = self.currentRate
self.seekRate = .x4
self.control?(.setBaseRate(self.seekRate))
let seekTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: true, completion: { [weak self] in
if let strongSelf = self {
if strongSelf.seekRate == .x4 {
strongSelf.seekRate = .x8
} else if strongSelf.seekRate == .x8 {
strongSelf.seekRate = .x16
}
strongSelf.control?(.setBaseRate(strongSelf.seekRate))
if strongSelf.seekRate == .x16 {
strongSelf.seekTimer?.invalidate()
strongSelf.seekTimer = nil
}
}
}, queue: Queue.mainQueue())
self.seekTimer = seekTimer
seekTimer.start()
case .ended, .cancelled:
self.backwardButton.isPressing = false
self.control?(.setBaseRate(self.previousRate ?? .x1))
self.seekTimer?.invalidate()
self.seekTimer = nil
default:
break
}
}
@objc private func seekForwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
self.forwardButton.isPressing = true
self.previousRate = self.currentRate
self.seekRate = .x4
self.control?(.setBaseRate(self.seekRate))
let seekTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: true, completion: { [weak self] in
if let strongSelf = self {
if strongSelf.seekRate == .x4 {
strongSelf.seekRate = .x8
} else if strongSelf.seekRate == .x8 {
strongSelf.seekRate = .x16
}
strongSelf.control?(.setBaseRate(strongSelf.seekRate))
if strongSelf.seekRate == .x16 {
strongSelf.seekTimer?.invalidate()
strongSelf.seekTimer = nil
}
}
}, queue: Queue.mainQueue())
self.seekTimer = seekTimer
seekTimer.start()
case .ended, .cancelled:
self.forwardButton.isPressing = false
self.control?(.setBaseRate(self.previousRate ?? .x1))
self.seekTimer?.invalidate()
self.seekTimer = nil
default:
break
}
} }
func updatePresentationData(_ presentationData: PresentationData) { func updatePresentationData(_ presentationData: PresentationData) {
@ -419,6 +521,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
} }
self.presentationData = presentationData self.presentationData = presentationData
self.playPauseButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.backwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.forwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.backgroundNode.image = generateBackground(theme: presentationData.theme) self.backgroundNode.image = generateBackground(theme: presentationData.theme)
self.collapseNode.setImage(generateCollapseIcon(theme: presentationData.theme), for: []) self.collapseNode.setImage(generateCollapseIcon(theme: presentationData.theme), for: [])
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: []) self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
@ -456,7 +562,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0 let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
let (titleString, descriptionString) = stringsForDisplayData(self.displayData, presentationData: self.presentationData) let (titleString, descriptionString, hasArtist) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
self.artistButton.isUserInteractionEnabled = hasArtist
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode) let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
@ -465,9 +572,12 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size)) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size))
let _ = titleApply() let _ = titleApply()
transition.updateFrame(node: self.descriptionNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 26.0), size: descriptionLayout.size)) let descriptionFrame = CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 24.0), size: descriptionLayout.size)
transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame)
let _ = descriptionApply() let _ = descriptionApply()
self.artistButton.frame = descriptionFrame.insetBy(dx: -8.0, dy: -8.0)
var albumArt: SharedMediaPlaybackAlbumArt? var albumArt: SharedMediaPlaybackAlbumArt?
if let displayData = self.displayData { if let displayData = self.displayData {
switch displayData { switch displayData {
@ -753,6 +863,13 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
} }
} }
@objc func artistPressed() {
let (_, descriptionString, _) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
if let artist = descriptionString?.string {
self.requestSearchByArtist?(artist)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event) let result = super.hitTest(point, with: event)
if result == self.view { if result == self.view {

View File

@ -219,6 +219,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
nextRate = .x2 nextRate = .x2
case .x2: case .x2:
nextRate = .x1 nextRate = .x1
default:
nextRate = .x1
} }
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
return settings.withUpdatedVoicePlaybackRate(nextRate) return settings.withUpdatedVoicePlaybackRate(nextRate)

View File

@ -950,6 +950,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}) })
} }
public func openSearch(filter: ChatListSearchFilter, query: String?) {
if let rootController = self.mainWindow?.viewController as? TelegramRootController {
rootController.openChatsController(activateSearch: true, filter: filter, query: query)
}
}
public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) { public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) {
self.navigateToChatImpl(accountId, peerId, messageId) self.navigateToChatImpl(accountId, peerId, messageId)
} }

View File

@ -227,6 +227,8 @@ final class SharedMediaPlayer {
rateValue = 1.0 rateValue = 1.0
case .x2: case .x2:
rateValue = 1.8 rateValue = 1.8
default:
rateValue = 1.0
} }
} }
@ -446,6 +448,12 @@ final class SharedMediaPlayer {
rateValue = 1.0 rateValue = 1.0
case .x2: case .x2:
rateValue = 1.8 rateValue = 1.8
case .x4:
rateValue = 4.0
case .x8:
rateValue = 8.0
case .x16:
rateValue = 16.0
} }
switch playbackItem { switch playbackItem {
case let .audio(player): case let .audio(player):

View File

@ -144,7 +144,7 @@ public final class TelegramRootController: NavigationController {
rootTabController.setControllers(controllers, selectedIndex: nil) rootTabController.setControllers(controllers, selectedIndex: nil)
} }
public func openChatsController(activateSearch: Bool) { public func openChatsController(activateSearch: Bool, filter: ChatListSearchFilter = .chats, query: String? = nil) {
guard let rootTabController = self.rootTabController else { guard let rootTabController = self.rootTabController else {
return return
} }
@ -158,7 +158,7 @@ public final class TelegramRootController: NavigationController {
} }
if activateSearch { if activateSearch {
self.chatListController?.activateSearch() self.chatListController?.activateSearch(filter: filter, query: query)
} }
} }

View File

@ -17,6 +17,9 @@ public enum MusicPlaybackSettingsLooping: Int32 {
public enum AudioPlaybackRate: Int32 { public enum AudioPlaybackRate: Int32 {
case x1 = 1000 case x1 = 1000
case x2 = 2000 case x2 = 2000
case x4 = 4000
case x8 = 8000
case x16 = 16000
var doubleValue: Double { var doubleValue: Double {
return Double(self.rawValue) / 1000.0 return Double(self.rawValue) / 1000.0