mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various UI improvements
This commit is contained in:
parent
1f23769a5b
commit
5459c357d4
@ -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";
|
||||
|
@ -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<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -109,8 +109,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
private var stateValue = ChatListSearchContainerNodeSearchState()
|
||||
private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>()
|
||||
|
||||
private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id)
|
||||
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>(.filter(ChatListSearchFilter.chats.id))
|
||||
private var selectedFilterKey: ChatListSearchFilterEntryId?
|
||||
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>()
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ objc_library(
|
||||
],
|
||||
weak_sdk_frameworks = [
|
||||
"Vision",
|
||||
"PhotosUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#import "TGMediaAssetsPickerController.h"
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
#import <PhotosUI/PhotosUI.h>
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import "TGMediaGroupsController.h"
|
||||
@ -26,14 +28,145 @@
|
||||
#import <LegacyComponents/TGVideoEditAdjustments.h>
|
||||
#import <LegacyComponents/TGPaintingData.h>
|
||||
|
||||
#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 () <UINavigationControllerDelegate, ASWatcher>
|
||||
{
|
||||
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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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! {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user