From 5459c357d4525274dda9ea8ed8758e6d7ac01fd5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 25 Dec 2020 20:16:12 +0400 Subject: [PATCH 1/2] Various UI improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/AccountContext.swift | 33 +++ .../Sources/ChatListController.swift | 2 +- .../Sources/ChatListController.swift | 12 +- .../Sources/ChatListControllerNode.swift | 4 +- .../Sources/ChatListSearchContainerNode.swift | 33 ++- .../ChatListSearchFiltersContainerNode.swift | 33 +-- .../Sources/ChatListSearchListPaneNode.swift | 2 + .../ChatListSearchPaneContainerNode.swift | 2 +- submodules/LegacyComponents/BUILD | 1 + .../TGMediaAssetsController.h | 3 +- .../TGMediaPickerController.h | 2 + .../Sources/TGMediaAssetsController.m | 225 +++++++++++++++++- .../Sources/TGMediaGroupsController.h | 1 + .../Sources/TGMediaGroupsController.m | 12 +- .../Sources/TGMediaPickerController.m | 9 +- .../TelegramInitializeLegacyComponents.swift | 2 +- .../MediaNavigationAccessoryHeaderNode.swift | 4 + .../Sources/TelegramBaseController.swift | 4 +- .../TelegramUI/Sources/IconButtonNode.swift | 48 +++- .../OverlayAudioPlayerController.swift | 5 + .../OverlayAudioPlayerControllerNode.swift | 8 +- .../Sources/OverlayPlayerControlsNode.swift | 129 +++++++++- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 2 + .../Sources/SharedAccountContext.swift | 6 + .../Sources/SharedMediaPlayer.swift | 8 + .../Sources/TelegramRootController.swift | 4 +- .../Sources/MusicPlaybackSettings.swift | 3 + 28 files changed, 538 insertions(+), 61 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index eede724e1f..0cf3ff695c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5668,6 +5668,8 @@ Sorry for the inconvenience."; "Media.LimitedAccessTitle" = "Limited Access to Media"; "Media.LimitedAccessText" = "You've given Telegram access only to select number of photos."; "Media.LimitedAccessManage" = "Manage"; +"Media.LimitedAccessSelectMore" = "Select More Photos..."; +"Media.LimitedAccessChangeSettings" = "Change Settings"; "VoiceChat.StatusSpeaking" = "speaking"; "VoiceChat.StatusListening" = "listening"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index a4ca97865a..0d713721ef 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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 public enum OpenWalletContext { case generic @@ -526,6 +558,7 @@ public protocol SharedAccountContext: class { func updateNotificationTokensRegistration() func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable 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 openChatMessage(_ params: OpenChatMessageParams) -> Bool func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> diff --git a/submodules/AccountContext/Sources/ChatListController.swift b/submodules/AccountContext/Sources/ChatListController.swift index 45e83f463a..41778e46c4 100644 --- a/submodules/AccountContext/Sources/ChatListController.swift +++ b/submodules/AccountContext/Sources/ChatListController.swift @@ -8,7 +8,7 @@ public protocol ChatListController: ViewController { var lockViewFrame: CGRect? { get } var isSearchActive: Bool { get } - func activateSearch() + func activateSearch(filter: ChatListSearchFilter, query: String?) func deactivateSearch(animated: Bool) func activateCompose() func maybeAskForPeerChatRemoval(peer: RenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 2e560c5298..37939de157 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1679,7 +1679,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public private(set) var isSearchActive: Bool = false - public func activateSearch() { + public func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil) { 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)) |> deliverOnMainQueue).start(next: { [weak self] _, chatListView in @@ -1703,7 +1703,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } 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 if displaySearchFilters { strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false) @@ -1714,6 +1714,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController activate() + if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { + searchContentNode.search(filter: filter, query: query) + } + if !tabsIsEmpty { 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) @@ -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) + } } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index ec1fa2c7a0..f0d60f4411 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -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 { return nil } @@ -1156,7 +1156,7 @@ final class ChatListControllerNode: ASDisplayNode { 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) }, openDisabledPeer: { _ in }, openRecentPeerOptions: { [weak self] peer in diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 145bd76645..b94b86b190 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -109,8 +109,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private var stateValue = ChatListSearchContainerNodeSearchState() private let statePromise = ValuePromise() - private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id) - private var selectedFilterKeyPromise = Promise(.filter(ChatListSearchFilter.chats.id)) + private var selectedFilterKey: ChatListSearchFilterEntryId? + private var selectedFilterKeyPromise = Promise() private var transitionFraction: CGFloat = 0.0 private var didSetReady: Bool = false @@ -121,7 +121,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo 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.peersFilter = filter self.groupId = groupId @@ -129,6 +129,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.navigationController = navigationController self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.selectedFilterKey = .filter(initialFilter.id) + self.selectedFilterKeyPromise.set(.single(self.selectedFilterKey)) + self.openMessage = originalOpenMessage self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay @@ -293,6 +296,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } + self.filterContainerNode.filterPressed?(initialFilter) + let suggestedPeers = self.searchQuery.get() |> mapToSignal { query -> Signal<[Peer], NoError> in 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))) } + + 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) { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) diff --git a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift index 4d18b61028..2224d24af0 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift @@ -6,38 +6,7 @@ import SyncCore import Postbox import TelegramCore import TelegramPresentationData - -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 - } - } -} +import AccountContext private final class ItemNode: ASDisplayNode { private let pressed: () -> Void diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 0b4c7be8b1..9a821d9103 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1730,6 +1730,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { nextRate = .x2 case .x2: nextRate = .x1 + default: + nextRate = .x1 } transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in return settings.withUpdatedVoicePlaybackRate(nextRate) diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index f1236e480a..99712212d2 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -316,7 +316,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD self.currentPaneKey = nil self.pendingSwitchToPaneKey = nil } - } else if self.currentPaneKey == nil { + } else if self.currentPaneKey == nil && self.pendingSwitchToPaneKey == nil { self.pendingSwitchToPaneKey = availablePanes.first } diff --git a/submodules/LegacyComponents/BUILD b/submodules/LegacyComponents/BUILD index 81a3745ef8..d1bfde2cc1 100644 --- a/submodules/LegacyComponents/BUILD +++ b/submodules/LegacyComponents/BUILD @@ -46,6 +46,7 @@ objc_library( ], weak_sdk_frameworks = [ "Vision", + "PhotosUI", ], visibility = [ "//visibility:public", diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 052f5e14c4..9f16354018 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -32,6 +32,7 @@ typedef enum @property (nonatomic, readonly) UIColor *textColor; @property (nonatomic, readonly) UIColor *secondaryTextColor; @property (nonatomic, readonly) UIColor *accentColor; +@property (nonatomic, readonly) UIColor *destructiveColor; @property (nonatomic, readonly) UIColor *barBackgroundColor; @property (nonatomic, readonly) UIColor *barSeparatorColor; @property (nonatomic, readonly) UIColor *navigationTitleColor; @@ -42,7 +43,7 @@ typedef enum @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 diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h index ed270d5314..cb68e9e24c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h @@ -36,6 +36,8 @@ @property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); +@property (nonatomic, assign) CGFloat topInset; + @property (nonatomic, strong) TGMediaAssetsPallete *pallete; @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 390b6a425f..35e3b13369 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -1,5 +1,7 @@ #import "TGMediaAssetsPickerController.h" +#import +#import #import "LegacyComponentsInternal.h" #import "TGMediaGroupsController.h" @@ -26,14 +28,145 @@ #import #import +#import "TGModernButton.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 () { TGMediaAssetsControllerIntent _intent; TGMediaPickerToolbarView *_toolbarView; + TGMediaPickerAccessView *_accessView; + SMetaDisposable *_groupingChangedDisposable; SMetaDisposable *_selectionChangedDisposable; SMetaDisposable *_timersChangedDisposable; @@ -456,6 +589,8 @@ _toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; if ((_intent != TGMediaAssetsControllerSendFileIntent && _intent != TGMediaAssetsControllerSendMediaIntent && _intent != TGMediaAssetsControllerPassportMultipleIntent) || _selectionContext == nil) [_toolbarView setRightButtonHidden:true]; + + __weak TGMediaAssetsController *weakSelf = self; if (_selectionContext.allowGrouping) { [_toolbarView setCenterButtonImage:TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete != nil ? _pallete.secondaryTextColor : UIColorRGB(0x858e99))]; @@ -463,7 +598,6 @@ [_toolbarView setCenterButtonHidden:true animated:false]; [_toolbarView setCenterButtonSelected:_selectionContext.grouping]; - __weak TGMediaAssetsController *weakSelf = self; _toolbarView.centerPressed = ^ { __strong TGMediaAssetsController *strongSelf = weakSelf; @@ -472,6 +606,73 @@ }; } [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 @@ -509,6 +710,12 @@ [_editingContext clearPaintingData]; } +- (void)setPallete:(TGMediaAssetsPallete *)pallete { + _pallete = pallete; + + [_accessView setPallete:pallete]; +} + - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; @@ -522,7 +729,20 @@ hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON; _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) return; @@ -1384,7 +1604,7 @@ @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]; pallete->_isDark = dark; @@ -1394,6 +1614,7 @@ pallete->_textColor = textColor; pallete->_secondaryTextColor = secondaryTextColor; pallete->_accentColor = accentColor; + pallete->_destructiveColor = destructiveColor; pallete->_barBackgroundColor = barBackgroundColor; pallete->_barSeparatorColor = barSeparatorColor; pallete->_navigationTitleColor = navigationTitleColor; diff --git a/submodules/LegacyComponents/Sources/TGMediaGroupsController.h b/submodules/LegacyComponents/Sources/TGMediaGroupsController.h index 3abfb2ae02..0b1864ca31 100644 --- a/submodules/LegacyComponents/Sources/TGMediaGroupsController.h +++ b/submodules/LegacyComponents/Sources/TGMediaGroupsController.h @@ -8,6 +8,7 @@ @property (nonatomic, assign) bool localMediaCacheEnabled; @property (nonatomic, assign) bool liveVideoUploadEnabled; @property (nonatomic, assign) bool captionsEnabled; +@property (nonatomic, assign) CGFloat topInset; @property (nonatomic, strong) TGMediaAssetsPallete *pallete; diff --git a/submodules/LegacyComponents/Sources/TGMediaGroupsController.m b/submodules/LegacyComponents/Sources/TGMediaGroupsController.m index eeaac76abf..c079d3ce7d 100644 --- a/submodules/LegacyComponents/Sources/TGMediaGroupsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaGroupsController.m @@ -53,7 +53,6 @@ if (iosMajorVersion() >= 11) _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; _tableView.alwaysBounceVertical = true; - _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _tableView.backgroundColor = self.view.backgroundColor; _tableView.delaysContentTouches = true; _tableView.canCancelContentTouches = true; @@ -71,6 +70,17 @@ [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 { if (iosMajorVersion() >= 9) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerController.m b/submodules/LegacyComponents/Sources/TGMediaPickerController.m index acd0d7af81..7e90bc9fd0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerController.m @@ -110,7 +110,7 @@ UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation]; 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; _collectionView.frame = collectionViewFrame; } @@ -280,6 +280,11 @@ [_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 { [super layoutControllerForSize:size duration:duration]; @@ -308,7 +313,7 @@ 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; _collectionView.frame = frame; diff --git a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift index 19bf7578d7..16a8c05cab 100644 --- a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift +++ b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift @@ -288,7 +288,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone let navigationBar = presentationTheme.rootController.navigationBar 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! { diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index 7bf5843296..3be5e77363 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -189,6 +189,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast 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: []) case .x2: self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) + default: + break } } if let (size, leftInset, rightInset) = self.validLayout { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 234856ab69..3d92ba83e8 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -663,6 +663,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { nextRate = .x2 case .x2: nextRate = .x1 + default: + nextRate = .x1 } transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in 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)) } } diff --git a/submodules/TelegramUI/Sources/IconButtonNode.swift b/submodules/TelegramUI/Sources/IconButtonNode.swift index a28e62d05f..917426ff40 100644 --- a/submodules/TelegramUI/Sources/IconButtonNode.swift +++ b/submodules/TelegramUI/Sources/IconButtonNode.swift @@ -3,8 +3,11 @@ import UIKit import AsyncDisplayKit import Display +private let circleDiameter: CGFloat = 80.0 + final class IconButtonNode: HighlightTrackingButtonNode { private let iconNode: ASImageNode + private var circleNode: ASImageNode? var icon: UIImage? { 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 { didSet { self.alpha = self.isEnabled ? 1.0 : 0.4 } } + var isPressing = false { + didSet { + if self.isPressing != oldValue && !self.isPressing { + self.highligthedChanged(false) + } + } + } + init() { self.iconNode = ASImageNode() self.iconNode.isLayerBacked = true @@ -33,11 +68,17 @@ final class IconButtonNode: HighlightTrackingButtonNode { self.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { 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) - } else { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .spring) + if let circleNode = strongSelf.circleNode { + 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) + 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 { 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) } } diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift index f46647013e..55908db048 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift @@ -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()) diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 9ad7121488..aed16bc4e0 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -20,6 +20,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu private let type: MediaManagerPlayerType private let requestDismiss: () -> Void private let requestShare: (MessageId) -> Void + private let requestSearchByArtist: (String) -> Void private let playlistLocation: SharedMediaPlaylistLocation? private let isGlobalSearch: Bool @@ -42,13 +43,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu private var presentationDataDisposable: Disposable? 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.peerId = peerId self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.type = type self.requestDismiss = requestDismiss self.requestShare = requestShare + self.requestSearchByArtist = requestSearchByArtist self.playlistLocation = playlistLocation if case .regular = initialOrder { @@ -224,6 +226,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu self?.requestShare(messageId) } + self.controlsNode.requestSearchByArtist = { [weak self] artist in + self?.requestSearchByArtist(artist) + } + self.controlsNode.updateOrder = { [weak self] order in if let strongSelf = self { let reversed: Bool diff --git a/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift b/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift index 218ac7cd11..f85da1cb22 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift @@ -57,12 +57,13 @@ private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat { return size.width } -private let titleFont = Font.semibold(17.0) -private let descriptionFont = Font.regular(17.0) +private let titleFont = Font.semibold(18.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 descriptionString: NSAttributedString? + var hasArtist = false if let data = data { let titleText: String @@ -71,6 +72,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres case let .music(title, performer, _, _): titleText = title ?? presentationData.strings.MediaPlayer_UnknownTrack subtitleText = performer ?? presentationData.strings.MediaPlayer_UnknownArtist + hasArtist = performer != nil case .voice, .instantVideo: titleText = "" subtitleText = "" @@ -80,7 +82,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: presentationData.theme.list.itemSecondaryTextColor) } - return (titleString, descriptionString) + return (titleString, descriptionString, hasArtist) } final class OverlayPlayerControlsNode: ASDisplayNode { @@ -97,6 +99,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { private let titleNode: TextNode private let descriptionNode: TextNode private let shareNode: HighlightableButtonNode + private let artistButton: HighlightTrackingButtonNode private let scrubberNode: MediaPlayerScrubbingNode private let leftDurationLabel: MediaPlayerTimeTextNode @@ -105,6 +108,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode { private let backwardButton: IconButtonNode private let forwardButton: IconButtonNode + private var seekTimer: SwiftSignalKit.Timer? + private var seekRate: AudioPlaybackRate = .x2 + private var previousRate: AudioPlaybackRate? + private var currentIsPaused: Bool? private let playPauseButton: IconButtonNode @@ -124,6 +131,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { var requestCollapse: (() -> Void)? var requestShare: ((MessageId) -> Void)? + var requestSearchByArtist: ((String) -> Void)? var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)? var control: ((SharedMediaPlayerControlAction) -> Void)? @@ -167,6 +175,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode { self.descriptionNode.isUserInteractionEnabled = false self.descriptionNode.displaysAsynchronously = false + self.artistButton = HighlightTrackingButtonNode() + self.shareNode = HighlightableButtonNode() 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.titleNode) self.addSubnode(self.descriptionNode) + self.addSubnode(self.artistButton) self.addSubnode(self.shareNode) self.addSubnode(self.leftDurationLabel) @@ -400,6 +411,23 @@ final class OverlayPlayerControlsNode: ASDisplayNode { self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside) self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), 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 { @@ -411,6 +439,80 @@ final class OverlayPlayerControlsNode: ASDisplayNode { super.didLoad() 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) { @@ -419,6 +521,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode { } 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.collapseNode.setImage(generateCollapseIcon(theme: presentationData.theme), 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 (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 (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) @@ -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)) 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() + self.artistButton.frame = descriptionFrame.insetBy(dx: -8.0, dy: -8.0) + var albumArt: SharedMediaPlaybackAlbumArt? if let displayData = self.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? { let result = super.hitTest(point, with: event) if result == self.view { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index ccf1808e7a..dee5eafaa8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -219,6 +219,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { nextRate = .x2 case .x2: nextRate = .x1 + default: + nextRate = .x1 } transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in return settings.withUpdatedVoicePlaybackRate(nextRate) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 717e10c6d4..f1ef91bb18 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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?) { self.navigateToChatImpl(accountId, peerId, messageId) } diff --git a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift index 0b18602b0d..c9eba8d4c0 100644 --- a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift +++ b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift @@ -227,6 +227,8 @@ final class SharedMediaPlayer { rateValue = 1.0 case .x2: rateValue = 1.8 + default: + rateValue = 1.0 } } @@ -446,6 +448,12 @@ final class SharedMediaPlayer { rateValue = 1.0 case .x2: rateValue = 1.8 + case .x4: + rateValue = 4.0 + case .x8: + rateValue = 8.0 + case .x16: + rateValue = 16.0 } switch playbackItem { case let .audio(player): diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 99e3ae0cfd..b8e795c86e 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -144,7 +144,7 @@ public final class TelegramRootController: NavigationController { 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 { return } @@ -158,7 +158,7 @@ public final class TelegramRootController: NavigationController { } if activateSearch { - self.chatListController?.activateSearch() + self.chatListController?.activateSearch(filter: filter, query: query) } } diff --git a/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift b/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift index 8d6d48cd49..14cfe6a36c 100644 --- a/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift @@ -17,6 +17,9 @@ public enum MusicPlaybackSettingsLooping: Int32 { public enum AudioPlaybackRate: Int32 { case x1 = 1000 case x2 = 2000 + case x4 = 4000 + case x8 = 8000 + case x16 = 16000 var doubleValue: Double { return Double(self.rawValue) / 1000.0 From ea83772c8dfee5e7fe7450952ac35f3457bd12ab Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 25 Dec 2020 20:16:30 +0400 Subject: [PATCH 2/2] Fix voice chat mute button transitions --- .../Sources/VoiceChatActionButton.swift | 93 +++++++++++-------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift index 71acec2d2e..edb9183ef6 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift @@ -736,9 +736,15 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { self.maskBlobView.startAnimating() self.maskBlobView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak self] _ in - self?.maskBlobView.isHidden = true - self?.maskBlobView.stopAnimating() - self?.maskBlobView.layer.removeAllAnimations() + guard let strongSelf = self else { + return + } + if strongSelf.state != .connecting && strongSelf.state != .disabled { + return + } + strongSelf.maskBlobView.isHidden = true + strongSelf.maskBlobView.stopAnimating() + strongSelf.maskBlobView.layer.removeAllAnimations() }) CATransaction.begin() @@ -750,6 +756,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { growthAnimation.isRemovedOnCompletion = false CATransaction.setCompletionBlock { + self.animatingDisappearance = false + if self.state != .connecting && self.state != .disabled { + return + } CATransaction.begin() CATransaction.setDisableActions(true) self.disableGlowAnimations = false @@ -757,7 +767,6 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { self.maskCircleLayer.isHidden = true self.growingForegroundCircleLayer.isHidden = true self.growingForegroundCircleLayer.removeAllAnimations() - self.animatingDisappearance = false CATransaction.commit() } @@ -790,11 +799,13 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) CATransaction.setCompletionBlock { - CATransaction.begin() - CATransaction.setDisableActions(true) - self.disableGlowAnimations = false - self.foregroundCircleLayer.isHidden = true - CATransaction.commit() + if case .blob = self.state { + CATransaction.begin() + CATransaction.setDisableActions(true) + self.disableGlowAnimations = false + self.foregroundCircleLayer.isHidden = true + CATransaction.commit() + } } self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink") @@ -829,40 +840,44 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode { groupAnimation.duration = duration CATransaction.setCompletionBlock { - CATransaction.begin() - 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 { + if case .blob = self.state { CATransaction.begin() 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() } - - self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink") - CATransaction.commit() } self.maskProgressLayer.add(groupAnimation, forKey: "progressCompletion")