mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'ea83772c8dfee5e7fe7450952ac35f3457bd12ab'
This commit is contained in:
commit
2cf2f3fc96
@ -5668,6 +5668,8 @@ Sorry for the inconvenience.";
|
|||||||
"Media.LimitedAccessTitle" = "Limited Access to Media";
|
"Media.LimitedAccessTitle" = "Limited Access to Media";
|
||||||
"Media.LimitedAccessText" = "You've given Telegram access only to select number of photos.";
|
"Media.LimitedAccessText" = "You've given Telegram access only to select number of photos.";
|
||||||
"Media.LimitedAccessManage" = "Manage";
|
"Media.LimitedAccessManage" = "Manage";
|
||||||
|
"Media.LimitedAccessSelectMore" = "Select More Photos...";
|
||||||
|
"Media.LimitedAccessChangeSettings" = "Change Settings";
|
||||||
|
|
||||||
"VoiceChat.StatusSpeaking" = "speaking";
|
"VoiceChat.StatusSpeaking" = "speaking";
|
||||||
"VoiceChat.StatusListening" = "listening";
|
"VoiceChat.StatusListening" = "listening";
|
||||||
|
@ -466,6 +466,38 @@ public final class ContactSelectionControllerParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ChatListSearchFilter: Equatable {
|
||||||
|
case chats
|
||||||
|
case media
|
||||||
|
case links
|
||||||
|
case files
|
||||||
|
case music
|
||||||
|
case voice
|
||||||
|
case peer(PeerId, Bool, String, String)
|
||||||
|
case date(Int32?, Int32, String)
|
||||||
|
|
||||||
|
public var id: Int32 {
|
||||||
|
switch self {
|
||||||
|
case .chats:
|
||||||
|
return 0
|
||||||
|
case .media:
|
||||||
|
return 1
|
||||||
|
case .links:
|
||||||
|
return 2
|
||||||
|
case .files:
|
||||||
|
return 3
|
||||||
|
case .music:
|
||||||
|
return 4
|
||||||
|
case .voice:
|
||||||
|
return 5
|
||||||
|
case let .peer(peerId, _, _, _):
|
||||||
|
return peerId.id
|
||||||
|
case let .date(_, date, _):
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if ENABLE_WALLET
|
#if ENABLE_WALLET
|
||||||
public enum OpenWalletContext {
|
public enum OpenWalletContext {
|
||||||
case generic
|
case generic
|
||||||
@ -526,6 +558,7 @@ public protocol SharedAccountContext: class {
|
|||||||
func updateNotificationTokensRegistration()
|
func updateNotificationTokensRegistration()
|
||||||
func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable
|
func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable
|
||||||
func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem)
|
func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem)
|
||||||
|
func openSearch(filter: ChatListSearchFilter, query: String?)
|
||||||
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
|
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
|
||||||
func openChatMessage(_ params: OpenChatMessageParams) -> Bool
|
func openChatMessage(_ params: OpenChatMessageParams) -> Bool
|
||||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||||
|
@ -8,7 +8,7 @@ public protocol ChatListController: ViewController {
|
|||||||
var lockViewFrame: CGRect? { get }
|
var lockViewFrame: CGRect? { get }
|
||||||
|
|
||||||
var isSearchActive: Bool { get }
|
var isSearchActive: Bool { get }
|
||||||
func activateSearch()
|
func activateSearch(filter: ChatListSearchFilter, query: String?)
|
||||||
func deactivateSearch(animated: Bool)
|
func deactivateSearch(animated: Bool)
|
||||||
func activateCompose()
|
func activateCompose()
|
||||||
func maybeAskForPeerChatRemoval(peer: RenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
|
func maybeAskForPeerChatRemoval(peer: RenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
|
||||||
|
@ -1679,7 +1679,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
public private(set) var isSearchActive: Bool = false
|
public private(set) var isSearchActive: Bool = false
|
||||||
public func activateSearch() {
|
public func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil) {
|
||||||
if self.displayNavigationBar {
|
if self.displayNavigationBar {
|
||||||
let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(tagSummary: nil, actionsSummary: nil)) |> take(1))
|
let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(tagSummary: nil, actionsSummary: nil)) |> take(1))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] _, chatListView in
|
|> deliverOnMainQueue).start(next: { [weak self] _, chatListView in
|
||||||
@ -1703,7 +1703,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let searchContentNode = strongSelf.searchContentNode {
|
if let searchContentNode = strongSelf.searchContentNode {
|
||||||
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, navigationController: strongSelf.navigationController as? NavigationController) {
|
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) {
|
||||||
let (filterContainerNode, activate) = filterContainerNodeAndActivate
|
let (filterContainerNode, activate) = filterContainerNodeAndActivate
|
||||||
if displaySearchFilters {
|
if displaySearchFilters {
|
||||||
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
|
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
|
||||||
@ -1714,6 +1714,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
activate()
|
activate()
|
||||||
|
|
||||||
|
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||||
|
searchContentNode.search(filter: filter, query: query)
|
||||||
|
}
|
||||||
|
|
||||||
if !tabsIsEmpty {
|
if !tabsIsEmpty {
|
||||||
Queue.mainQueue().after(0.01) {
|
Queue.mainQueue().after(0.01) {
|
||||||
filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 38.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 38.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
@ -1739,6 +1743,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if self.isSearchActive {
|
||||||
|
if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||||
|
searchContentNode.search(filter: filter, query: query)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1146,7 +1146,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
|
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
|
||||||
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1156,7 +1156,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
filter.insert(.excludeRecent)
|
filter.insert(.excludeRecent)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentNode = ChatListSearchContainerNode(context: self.context, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, openPeer: { [weak self] peer, dismissSearch in
|
let contentNode = ChatListSearchContainerNode(context: self.context, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, initialFilter: initialFilter, openPeer: { [weak self] peer, dismissSearch in
|
||||||
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
|
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
|
||||||
}, openDisabledPeer: { _ in
|
}, openDisabledPeer: { _ in
|
||||||
}, openRecentPeerOptions: { [weak self] peer in
|
}, openRecentPeerOptions: { [weak self] peer in
|
||||||
|
@ -109,8 +109,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
private var stateValue = ChatListSearchContainerNodeSearchState()
|
private var stateValue = ChatListSearchContainerNodeSearchState()
|
||||||
private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>()
|
private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>()
|
||||||
|
|
||||||
private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id)
|
private var selectedFilterKey: ChatListSearchFilterEntryId?
|
||||||
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>(.filter(ChatListSearchFilter.chats.id))
|
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>()
|
||||||
private var transitionFraction: CGFloat = 0.0
|
private var transitionFraction: CGFloat = 0.0
|
||||||
|
|
||||||
private var didSetReady: Bool = false
|
private var didSetReady: Bool = false
|
||||||
@ -121,7 +121,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, displaySearchFilters: Bool, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peersFilter = filter
|
self.peersFilter = filter
|
||||||
self.groupId = groupId
|
self.groupId = groupId
|
||||||
@ -129,6 +129,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
self.navigationController = navigationController
|
self.navigationController = navigationController
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
self.selectedFilterKey = .filter(initialFilter.id)
|
||||||
|
self.selectedFilterKeyPromise.set(.single(self.selectedFilterKey))
|
||||||
|
|
||||||
self.openMessage = originalOpenMessage
|
self.openMessage = originalOpenMessage
|
||||||
self.present = present
|
self.present = present
|
||||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||||
@ -293,6 +296,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.filterContainerNode.filterPressed?(initialFilter)
|
||||||
|
|
||||||
let suggestedPeers = self.searchQuery.get()
|
let suggestedPeers = self.searchQuery.get()
|
||||||
|> mapToSignal { query -> Signal<[Peer], NoError> in
|
|> mapToSignal { query -> Signal<[Peer], NoError> in
|
||||||
if let query = query {
|
if let query = query {
|
||||||
@ -489,6 +494,28 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
|
|
||||||
self.suggestedDates.set(.single(suggestDates(for: text, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)))
|
self.suggestedDates.set(.single(suggestDates(for: text, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func search(filter: ChatListSearchFilter, query: String?) {
|
||||||
|
let key: ChatListSearchPaneKey
|
||||||
|
switch filter {
|
||||||
|
case .media:
|
||||||
|
key = .media
|
||||||
|
case .links:
|
||||||
|
key = .links
|
||||||
|
case .files:
|
||||||
|
key = .files
|
||||||
|
case .music:
|
||||||
|
key = .music
|
||||||
|
case .voice:
|
||||||
|
key = .voice
|
||||||
|
default:
|
||||||
|
key = .chats
|
||||||
|
}
|
||||||
|
self.paneContainerNode.requestSelectPane(key)
|
||||||
|
self.updateSearchOptions(nil)
|
||||||
|
self.searchTextUpdated(text: query ?? "")
|
||||||
|
self.setQuery?(nil, [], query ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||||
|
@ -6,38 +6,7 @@ import SyncCore
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
enum ChatListSearchFilter: Equatable {
|
|
||||||
case chats
|
|
||||||
case media
|
|
||||||
case links
|
|
||||||
case files
|
|
||||||
case music
|
|
||||||
case voice
|
|
||||||
case peer(PeerId, Bool, String, String)
|
|
||||||
case date(Int32?, Int32, String)
|
|
||||||
|
|
||||||
var id: Int32 {
|
|
||||||
switch self {
|
|
||||||
case .chats:
|
|
||||||
return 0
|
|
||||||
case .media:
|
|
||||||
return 1
|
|
||||||
case .links:
|
|
||||||
return 2
|
|
||||||
case .files:
|
|
||||||
return 3
|
|
||||||
case .music:
|
|
||||||
return 4
|
|
||||||
case .voice:
|
|
||||||
return 5
|
|
||||||
case let .peer(peerId, _, _, _):
|
|
||||||
return peerId.id
|
|
||||||
case let .date(_, date, _):
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ItemNode: ASDisplayNode {
|
private final class ItemNode: ASDisplayNode {
|
||||||
private let pressed: () -> Void
|
private let pressed: () -> Void
|
||||||
|
@ -1730,6 +1730,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
nextRate = .x2
|
nextRate = .x2
|
||||||
case .x2:
|
case .x2:
|
||||||
nextRate = .x1
|
nextRate = .x1
|
||||||
|
default:
|
||||||
|
nextRate = .x1
|
||||||
}
|
}
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||||
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
||||||
|
@ -316,7 +316,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
|||||||
self.currentPaneKey = nil
|
self.currentPaneKey = nil
|
||||||
self.pendingSwitchToPaneKey = nil
|
self.pendingSwitchToPaneKey = nil
|
||||||
}
|
}
|
||||||
} else if self.currentPaneKey == nil {
|
} else if self.currentPaneKey == nil && self.pendingSwitchToPaneKey == nil {
|
||||||
self.pendingSwitchToPaneKey = availablePanes.first
|
self.pendingSwitchToPaneKey = availablePanes.first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ objc_library(
|
|||||||
],
|
],
|
||||||
weak_sdk_frameworks = [
|
weak_sdk_frameworks = [
|
||||||
"Vision",
|
"Vision",
|
||||||
|
"PhotosUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -32,6 +32,7 @@ typedef enum
|
|||||||
@property (nonatomic, readonly) UIColor *textColor;
|
@property (nonatomic, readonly) UIColor *textColor;
|
||||||
@property (nonatomic, readonly) UIColor *secondaryTextColor;
|
@property (nonatomic, readonly) UIColor *secondaryTextColor;
|
||||||
@property (nonatomic, readonly) UIColor *accentColor;
|
@property (nonatomic, readonly) UIColor *accentColor;
|
||||||
|
@property (nonatomic, readonly) UIColor *destructiveColor;
|
||||||
@property (nonatomic, readonly) UIColor *barBackgroundColor;
|
@property (nonatomic, readonly) UIColor *barBackgroundColor;
|
||||||
@property (nonatomic, readonly) UIColor *barSeparatorColor;
|
@property (nonatomic, readonly) UIColor *barSeparatorColor;
|
||||||
@property (nonatomic, readonly) UIColor *navigationTitleColor;
|
@property (nonatomic, readonly) UIColor *navigationTitleColor;
|
||||||
@ -42,7 +43,7 @@ typedef enum
|
|||||||
|
|
||||||
@property (nonatomic, readonly) UIColor *maybeAccentColor;
|
@property (nonatomic, readonly) UIColor *maybeAccentColor;
|
||||||
|
|
||||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor;
|
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t));
|
@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t));
|
||||||
@property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t));
|
@property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t));
|
||||||
|
|
||||||
|
@property (nonatomic, assign) CGFloat topInset;
|
||||||
|
|
||||||
@property (nonatomic, strong) TGMediaAssetsPallete *pallete;
|
@property (nonatomic, strong) TGMediaAssetsPallete *pallete;
|
||||||
|
|
||||||
@property (nonatomic, readonly) TGMediaSelectionContext *selectionContext;
|
@property (nonatomic, readonly) TGMediaSelectionContext *selectionContext;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#import "TGMediaAssetsPickerController.h"
|
#import "TGMediaAssetsPickerController.h"
|
||||||
|
|
||||||
|
#import <Photos/Photos.h>
|
||||||
|
#import <PhotosUI/PhotosUI.h>
|
||||||
#import "LegacyComponentsInternal.h"
|
#import "LegacyComponentsInternal.h"
|
||||||
|
|
||||||
#import "TGMediaGroupsController.h"
|
#import "TGMediaGroupsController.h"
|
||||||
@ -26,14 +28,145 @@
|
|||||||
#import <LegacyComponents/TGVideoEditAdjustments.h>
|
#import <LegacyComponents/TGVideoEditAdjustments.h>
|
||||||
#import <LegacyComponents/TGPaintingData.h>
|
#import <LegacyComponents/TGPaintingData.h>
|
||||||
|
|
||||||
|
#import "TGModernButton.h"
|
||||||
#import "PGPhotoEditor.h"
|
#import "PGPhotoEditor.h"
|
||||||
|
|
||||||
|
@interface TGMediaPickerAccessView: UIView
|
||||||
|
{
|
||||||
|
TGMediaAssetsPallete *_pallete;
|
||||||
|
|
||||||
|
UIView *_backgroundView;
|
||||||
|
UIView *_separatorView;
|
||||||
|
UIView *_bottomSeparatorView;
|
||||||
|
UIImageView *_iconView;
|
||||||
|
UILabel *_labelView;
|
||||||
|
UILabel *_titleView;
|
||||||
|
UILabel *_textView;
|
||||||
|
TGModernButton * _buttonView;
|
||||||
|
|
||||||
|
CGSize _titleSize;
|
||||||
|
CGSize _textSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (nonatomic, assign) UIEdgeInsets safeAreaInset;
|
||||||
|
|
||||||
|
@property (nonatomic, copy) void (^pressed)(void);
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation TGMediaPickerAccessView
|
||||||
|
|
||||||
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
|
self = [super initWithFrame:frame];
|
||||||
|
if (self != nil) {
|
||||||
|
_backgroundView = [[UIView alloc] init];
|
||||||
|
_separatorView = [[UIView alloc] init];
|
||||||
|
_bottomSeparatorView = [[UIView alloc] init];
|
||||||
|
_iconView = [[UIImageView alloc] init];
|
||||||
|
_labelView = [[UILabel alloc] init];
|
||||||
|
_titleView = [[UILabel alloc] init];
|
||||||
|
_textView = [[UILabel alloc] init];
|
||||||
|
|
||||||
|
_labelView.font = TGSystemFontOfSize(14.0);
|
||||||
|
_labelView.text = @"!";
|
||||||
|
_labelView.textAlignment = NSTextAlignmentCenter;
|
||||||
|
|
||||||
|
_titleView.font = TGSemiboldSystemFontOfSize(17.0);
|
||||||
|
_titleView.text = TGLocalized(@"Media.LimitedAccessTitle");
|
||||||
|
_titleView.numberOfLines = 1;
|
||||||
|
|
||||||
|
_textView.font = TGSystemFontOfSize(14.0);
|
||||||
|
_textView.text = TGLocalized(@"Media.LimitedAccessText");
|
||||||
|
_textView.numberOfLines = 3;
|
||||||
|
|
||||||
|
_buttonView = [[TGModernButton alloc] init];
|
||||||
|
_buttonView.adjustsImageWhenHighlighted = false;
|
||||||
|
_buttonView.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
|
||||||
|
_buttonView.contentEdgeInsets = UIEdgeInsetsMake(0.0, 15.0, 0.0, 0.0);
|
||||||
|
_buttonView.titleLabel.font = TGSystemFontOfSize(17.0f);
|
||||||
|
_buttonView.highlightBackgroundColor = UIColorRGB(0xebebeb);
|
||||||
|
[_buttonView setTitle:TGLocalized(@"Media.LimitedAccessManage") forState:UIControlStateNormal];
|
||||||
|
[_buttonView addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
|
||||||
|
[self addSubview:_backgroundView];
|
||||||
|
[self addSubview:_separatorView];
|
||||||
|
[self addSubview:_bottomSeparatorView];
|
||||||
|
[self addSubview:_iconView];
|
||||||
|
[self addSubview:_labelView];
|
||||||
|
[self addSubview:_titleView];
|
||||||
|
[self addSubview:_textView];
|
||||||
|
[self addSubview:_buttonView];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)buttonPressed {
|
||||||
|
self.pressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPallete:(TGMediaAssetsPallete *)pallete {
|
||||||
|
_pallete = pallete;
|
||||||
|
|
||||||
|
_backgroundView.backgroundColor = pallete.backgroundColor;
|
||||||
|
_separatorView.backgroundColor = pallete.separatorColor;
|
||||||
|
_bottomSeparatorView.backgroundColor = pallete.separatorColor;
|
||||||
|
_titleView.textColor = pallete.textColor;
|
||||||
|
_textView.textColor = pallete.textColor;
|
||||||
|
_iconView.image = TGCircleImage(20.0, pallete.destructiveColor);
|
||||||
|
_labelView.textColor = pallete.badgeTextColor;
|
||||||
|
_buttonView.highlightBackgroundColor = pallete.selectionColor;
|
||||||
|
|
||||||
|
[_buttonView setTitleColor:pallete.accentColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGSize)sizeThatFits:(CGSize)size {
|
||||||
|
CGSize result = CGSizeMake(size.width, 0.0);
|
||||||
|
|
||||||
|
CGSize constrainedSize = CGSizeMake(size.width - 30.0, size.height);
|
||||||
|
CGSize titleSize = [_titleView sizeThatFits:constrainedSize];
|
||||||
|
CGSize textSize = [_textView sizeThatFits:constrainedSize];
|
||||||
|
|
||||||
|
result.height += titleSize.height;
|
||||||
|
result.height += textSize.height;
|
||||||
|
result.height += 45.0;
|
||||||
|
result.height += 39.0;
|
||||||
|
result.height = CGFloor(result.height);
|
||||||
|
|
||||||
|
_titleSize = titleSize;
|
||||||
|
_textSize = textSize;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setSafeAreaInset:(UIEdgeInsets)safeAreaInset {
|
||||||
|
_safeAreaInset = safeAreaInset;
|
||||||
|
[self layoutSubviews];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)layoutSubviews {
|
||||||
|
_backgroundView.frame = self.bounds;
|
||||||
|
|
||||||
|
_iconView.frame = CGRectMake(self.safeAreaInset.left + 15.0, 16.0, 20.0, 20.0);
|
||||||
|
_labelView.frame = _iconView.frame;
|
||||||
|
|
||||||
|
_buttonView.contentEdgeInsets = UIEdgeInsetsMake(0.0, self.safeAreaInset.left + 15.0, 0.0, 0.0);
|
||||||
|
_titleView.frame = CGRectMake(self.safeAreaInset.left + 42.0, 15.0, _titleSize.width, _titleSize.height);
|
||||||
|
_textView.frame = CGRectMake(self.safeAreaInset.left + 15.0, 46.0, _textSize.width, _textSize.height);
|
||||||
|
_separatorView.frame = CGRectMake(self.safeAreaInset.left + 15.0, self.bounds.size.height - 46.0, self.bounds.size.width, TGScreenPixel);
|
||||||
|
_buttonView.frame = CGRectMake(0.0, self.bounds.size.height - 46.0, self.bounds.size.width, 46.0);
|
||||||
|
_bottomSeparatorView.frame = CGRectMake(0.0, self.bounds.size.height - TGScreenPixel, self.bounds.size.width, TGScreenPixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@interface TGMediaAssetsController () <UINavigationControllerDelegate, ASWatcher>
|
@interface TGMediaAssetsController () <UINavigationControllerDelegate, ASWatcher>
|
||||||
{
|
{
|
||||||
TGMediaAssetsControllerIntent _intent;
|
TGMediaAssetsControllerIntent _intent;
|
||||||
|
|
||||||
TGMediaPickerToolbarView *_toolbarView;
|
TGMediaPickerToolbarView *_toolbarView;
|
||||||
|
|
||||||
|
TGMediaPickerAccessView *_accessView;
|
||||||
|
|
||||||
SMetaDisposable *_groupingChangedDisposable;
|
SMetaDisposable *_groupingChangedDisposable;
|
||||||
SMetaDisposable *_selectionChangedDisposable;
|
SMetaDisposable *_selectionChangedDisposable;
|
||||||
SMetaDisposable *_timersChangedDisposable;
|
SMetaDisposable *_timersChangedDisposable;
|
||||||
@ -456,6 +589,8 @@
|
|||||||
_toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
_toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
||||||
if ((_intent != TGMediaAssetsControllerSendFileIntent && _intent != TGMediaAssetsControllerSendMediaIntent && _intent != TGMediaAssetsControllerPassportMultipleIntent) || _selectionContext == nil)
|
if ((_intent != TGMediaAssetsControllerSendFileIntent && _intent != TGMediaAssetsControllerSendMediaIntent && _intent != TGMediaAssetsControllerPassportMultipleIntent) || _selectionContext == nil)
|
||||||
[_toolbarView setRightButtonHidden:true];
|
[_toolbarView setRightButtonHidden:true];
|
||||||
|
|
||||||
|
__weak TGMediaAssetsController *weakSelf = self;
|
||||||
if (_selectionContext.allowGrouping)
|
if (_selectionContext.allowGrouping)
|
||||||
{
|
{
|
||||||
[_toolbarView setCenterButtonImage:TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete != nil ? _pallete.secondaryTextColor : UIColorRGB(0x858e99))];
|
[_toolbarView setCenterButtonImage:TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete != nil ? _pallete.secondaryTextColor : UIColorRGB(0x858e99))];
|
||||||
@ -463,7 +598,6 @@
|
|||||||
[_toolbarView setCenterButtonHidden:true animated:false];
|
[_toolbarView setCenterButtonHidden:true animated:false];
|
||||||
[_toolbarView setCenterButtonSelected:_selectionContext.grouping];
|
[_toolbarView setCenterButtonSelected:_selectionContext.grouping];
|
||||||
|
|
||||||
__weak TGMediaAssetsController *weakSelf = self;
|
|
||||||
_toolbarView.centerPressed = ^
|
_toolbarView.centerPressed = ^
|
||||||
{
|
{
|
||||||
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
||||||
@ -472,6 +606,73 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
[self.view addSubview:_toolbarView];
|
[self.view addSubview:_toolbarView];
|
||||||
|
|
||||||
|
if (iosMajorVersion() >= 14 && [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite] == PHAuthorizationStatusLimited) {
|
||||||
|
_accessView = [[TGMediaPickerAccessView alloc] init];
|
||||||
|
_accessView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||||
|
_accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:self.interfaceOrientation hasOnScreenNavigation:hasOnScreenNavigation];
|
||||||
|
[_accessView setPallete:_pallete];
|
||||||
|
_accessView.pressed = ^{
|
||||||
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
||||||
|
if (strongSelf != nil) {
|
||||||
|
[strongSelf manageAccess];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
[self.view addSubview:_accessView];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)manageAccess
|
||||||
|
{
|
||||||
|
if (iosMajorVersion() < 14) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__weak TGMediaAssetsController *weakSelf = self;
|
||||||
|
|
||||||
|
TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
|
||||||
|
controller.dismissesByOutsideTap = true;
|
||||||
|
controller.narrowInLandscape = true;
|
||||||
|
__weak TGMenuSheetController *weakController = controller;
|
||||||
|
|
||||||
|
NSArray *items = @
|
||||||
|
[
|
||||||
|
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessSelectMore") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||||
|
{
|
||||||
|
__strong TGMenuSheetController *strongController = weakController;
|
||||||
|
if (strongController == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
||||||
|
if (strongSelf == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
[strongController dismissAnimated:true manual:false completion:nil];
|
||||||
|
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:strongSelf];
|
||||||
|
}],
|
||||||
|
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessChangeSettings") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||||
|
{
|
||||||
|
__strong TGMenuSheetController *strongController = weakController;
|
||||||
|
if (strongController == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
||||||
|
if (strongSelf == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
[strongController dismissAnimated:true manual:false completion:nil];
|
||||||
|
[[[LegacyComponentsGlobals provider] applicationInstance] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
||||||
|
}],
|
||||||
|
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
|
||||||
|
{
|
||||||
|
__strong TGMenuSheetController *strongController = weakController;
|
||||||
|
if (strongController != nil)
|
||||||
|
[strongController dismissAnimated:true];
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
[controller setItemViews:items];
|
||||||
|
[controller presentInViewController:self sourceView:self.view animated:true];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLoad
|
- (void)viewDidLoad
|
||||||
@ -509,6 +710,12 @@
|
|||||||
[_editingContext clearPaintingData];
|
[_editingContext clearPaintingData];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setPallete:(TGMediaAssetsPallete *)pallete {
|
||||||
|
_pallete = pallete;
|
||||||
|
|
||||||
|
[_accessView setPallete:pallete];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)viewDidLayoutSubviews
|
- (void)viewDidLayoutSubviews
|
||||||
{
|
{
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
@ -522,7 +729,20 @@
|
|||||||
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
|
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
|
||||||
|
|
||||||
_toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
_toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
||||||
|
_accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
||||||
|
|
||||||
|
if (_accessView != nil) {
|
||||||
|
CGSize accessSize = [_accessView sizeThatFits:self.view.frame.size];
|
||||||
|
_accessView.frame = CGRectMake(0.0, self.navigationBar.frame.size.height, self.view.frame.size.width, accessSize.height);
|
||||||
|
|
||||||
|
for (UIViewController *controller in self.viewControllers) {
|
||||||
|
if ([controller isKindOfClass:[TGMediaGroupsController class]]) {
|
||||||
|
((TGMediaGroupsController *)controller).topInset = accessSize.height;
|
||||||
|
} else if ([controller isKindOfClass:[TGMediaPickerController class]]) {
|
||||||
|
((TGMediaPickerController *)controller).topInset = accessSize.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (_searchController == nil)
|
if (_searchController == nil)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -1384,7 +1604,7 @@
|
|||||||
|
|
||||||
@implementation TGMediaAssetsPallete
|
@implementation TGMediaAssetsPallete
|
||||||
|
|
||||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor
|
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor
|
||||||
{
|
{
|
||||||
TGMediaAssetsPallete *pallete = [[TGMediaAssetsPallete alloc] init];
|
TGMediaAssetsPallete *pallete = [[TGMediaAssetsPallete alloc] init];
|
||||||
pallete->_isDark = dark;
|
pallete->_isDark = dark;
|
||||||
@ -1394,6 +1614,7 @@
|
|||||||
pallete->_textColor = textColor;
|
pallete->_textColor = textColor;
|
||||||
pallete->_secondaryTextColor = secondaryTextColor;
|
pallete->_secondaryTextColor = secondaryTextColor;
|
||||||
pallete->_accentColor = accentColor;
|
pallete->_accentColor = accentColor;
|
||||||
|
pallete->_destructiveColor = destructiveColor;
|
||||||
pallete->_barBackgroundColor = barBackgroundColor;
|
pallete->_barBackgroundColor = barBackgroundColor;
|
||||||
pallete->_barSeparatorColor = barSeparatorColor;
|
pallete->_barSeparatorColor = barSeparatorColor;
|
||||||
pallete->_navigationTitleColor = navigationTitleColor;
|
pallete->_navigationTitleColor = navigationTitleColor;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
@property (nonatomic, assign) bool localMediaCacheEnabled;
|
@property (nonatomic, assign) bool localMediaCacheEnabled;
|
||||||
@property (nonatomic, assign) bool liveVideoUploadEnabled;
|
@property (nonatomic, assign) bool liveVideoUploadEnabled;
|
||||||
@property (nonatomic, assign) bool captionsEnabled;
|
@property (nonatomic, assign) bool captionsEnabled;
|
||||||
|
@property (nonatomic, assign) CGFloat topInset;
|
||||||
|
|
||||||
@property (nonatomic, strong) TGMediaAssetsPallete *pallete;
|
@property (nonatomic, strong) TGMediaAssetsPallete *pallete;
|
||||||
|
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
if (iosMajorVersion() >= 11)
|
if (iosMajorVersion() >= 11)
|
||||||
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||||
_tableView.alwaysBounceVertical = true;
|
_tableView.alwaysBounceVertical = true;
|
||||||
_tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
||||||
_tableView.backgroundColor = self.view.backgroundColor;
|
_tableView.backgroundColor = self.view.backgroundColor;
|
||||||
_tableView.delaysContentTouches = true;
|
_tableView.delaysContentTouches = true;
|
||||||
_tableView.canCancelContentTouches = true;
|
_tableView.canCancelContentTouches = true;
|
||||||
@ -71,6 +70,17 @@
|
|||||||
[self controllerInsetUpdated:UIEdgeInsetsZero];
|
[self controllerInsetUpdated:UIEdgeInsetsZero];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setTopInset:(CGFloat)topInset {
|
||||||
|
_topInset = topInset;
|
||||||
|
[self viewDidLayoutSubviews];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLayoutSubviews {
|
||||||
|
[super viewDidLayoutSubviews];
|
||||||
|
|
||||||
|
_tableView.frame = CGRectMake(0.0, _topInset, self.view.bounds.size.width, self.view.bounds.size.height - _topInset);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)loadViewIfNeeded
|
- (void)loadViewIfNeeded
|
||||||
{
|
{
|
||||||
if (iosMajorVersion() >= 9)
|
if (iosMajorVersion() >= 9)
|
||||||
|
@ -110,7 +110,7 @@
|
|||||||
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
||||||
|
|
||||||
CGSize frameSize = self.view.frame.size;
|
CGSize frameSize = self.view.frame.size;
|
||||||
CGRect collectionViewFrame = CGRectMake(safeAreaInset.left, 0.0f, frameSize.width - safeAreaInset.left - safeAreaInset.right, frameSize.height);
|
CGRect collectionViewFrame = CGRectMake(safeAreaInset.left, _topInset, frameSize.width - safeAreaInset.left - safeAreaInset.right, frameSize.height - _topInset);
|
||||||
_collectionViewWidth = collectionViewFrame.size.width;
|
_collectionViewWidth = collectionViewFrame.size.width;
|
||||||
_collectionView.frame = collectionViewFrame;
|
_collectionView.frame = collectionViewFrame;
|
||||||
}
|
}
|
||||||
@ -280,6 +280,11 @@
|
|||||||
[_collectionView setContentOffset:contentOffset animated:false];
|
[_collectionView setContentOffset:contentOffset animated:false];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setTopInset:(CGFloat)topInset {
|
||||||
|
_topInset = topInset;
|
||||||
|
[self layoutControllerForSize:self.view.frame.size duration:0.0];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)layoutControllerForSize:(CGSize)size duration:(NSTimeInterval)duration
|
- (void)layoutControllerForSize:(CGSize)size duration:(NSTimeInterval)duration
|
||||||
{
|
{
|
||||||
[super layoutControllerForSize:size duration:duration];
|
[super layoutControllerForSize:size duration:duration];
|
||||||
@ -308,7 +313,7 @@
|
|||||||
|
|
||||||
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
||||||
|
|
||||||
CGRect frame = CGRectMake(safeAreaInset.left, 0, size.width - safeAreaInset.left - safeAreaInset.right, size.height);
|
CGRect frame = CGRectMake(safeAreaInset.left, _topInset, size.width - safeAreaInset.left - safeAreaInset.right, size.height - _topInset);
|
||||||
_collectionViewWidth = frame.size.width;
|
_collectionViewWidth = frame.size.width;
|
||||||
_collectionView.frame = frame;
|
_collectionView.frame = frame;
|
||||||
|
|
||||||
|
@ -288,7 +288,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone
|
|||||||
let navigationBar = presentationTheme.rootController.navigationBar
|
let navigationBar = presentationTheme.rootController.navigationBar
|
||||||
let tabBar = presentationTheme.rootController.tabBar
|
let tabBar = presentationTheme.rootController.tabBar
|
||||||
|
|
||||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, barBackgroundColor: tabBar.backgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, destructiveColor: theme.itemDestructiveColor, barBackgroundColor: tabBar.backgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkButtonPallete() -> TGCheckButtonPallete! {
|
func checkButtonPallete() -> TGCheckButtonPallete! {
|
||||||
|
@ -189,6 +189,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
|||||||
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
||||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
|
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
|
||||||
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,6 +385,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
|||||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
|
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
|
||||||
case .x2:
|
case .x2:
|
||||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
|
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let (size, leftInset, rightInset) = self.validLayout {
|
if let (size, leftInset, rightInset) = self.validLayout {
|
||||||
|
@ -663,6 +663,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
nextRate = .x2
|
nextRate = .x2
|
||||||
case .x2:
|
case .x2:
|
||||||
nextRate = .x1
|
nextRate = .x1
|
||||||
|
default:
|
||||||
|
nextRate = .x1
|
||||||
}
|
}
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||||
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
||||||
@ -813,7 +815,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
})]
|
})]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo) {
|
open func joinGroupCall(peerId: PeerId, info: GroupCallInfo) {
|
||||||
self.context.joinGroupCall(peerId: peerId, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash))
|
self.context.joinGroupCall(peerId: peerId, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -736,9 +736,15 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.maskBlobView.startAnimating()
|
self.maskBlobView.startAnimating()
|
||||||
self.maskBlobView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak self] _ in
|
self.maskBlobView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
self?.maskBlobView.isHidden = true
|
guard let strongSelf = self else {
|
||||||
self?.maskBlobView.stopAnimating()
|
return
|
||||||
self?.maskBlobView.layer.removeAllAnimations()
|
}
|
||||||
|
if strongSelf.state != .connecting && strongSelf.state != .disabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.maskBlobView.isHidden = true
|
||||||
|
strongSelf.maskBlobView.stopAnimating()
|
||||||
|
strongSelf.maskBlobView.layer.removeAllAnimations()
|
||||||
})
|
})
|
||||||
|
|
||||||
CATransaction.begin()
|
CATransaction.begin()
|
||||||
@ -750,6 +756,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
growthAnimation.isRemovedOnCompletion = false
|
growthAnimation.isRemovedOnCompletion = false
|
||||||
|
|
||||||
CATransaction.setCompletionBlock {
|
CATransaction.setCompletionBlock {
|
||||||
|
self.animatingDisappearance = false
|
||||||
|
if self.state != .connecting && self.state != .disabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
CATransaction.begin()
|
CATransaction.begin()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
self.disableGlowAnimations = false
|
self.disableGlowAnimations = false
|
||||||
@ -757,7 +767,6 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self.maskCircleLayer.isHidden = true
|
self.maskCircleLayer.isHidden = true
|
||||||
self.growingForegroundCircleLayer.isHidden = true
|
self.growingForegroundCircleLayer.isHidden = true
|
||||||
self.growingForegroundCircleLayer.removeAllAnimations()
|
self.growingForegroundCircleLayer.removeAllAnimations()
|
||||||
self.animatingDisappearance = false
|
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -790,11 +799,13 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||||
|
|
||||||
CATransaction.setCompletionBlock {
|
CATransaction.setCompletionBlock {
|
||||||
CATransaction.begin()
|
if case .blob = self.state {
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.begin()
|
||||||
self.disableGlowAnimations = false
|
CATransaction.setDisableActions(true)
|
||||||
self.foregroundCircleLayer.isHidden = true
|
self.disableGlowAnimations = false
|
||||||
CATransaction.commit()
|
self.foregroundCircleLayer.isHidden = true
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
|
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
|
||||||
@ -829,40 +840,44 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
groupAnimation.duration = duration
|
groupAnimation.duration = duration
|
||||||
|
|
||||||
CATransaction.setCompletionBlock {
|
CATransaction.setCompletionBlock {
|
||||||
CATransaction.begin()
|
if case .blob = self.state {
|
||||||
CATransaction.setDisableActions(true)
|
|
||||||
self.foregroundCircleLayer.isHidden = false
|
|
||||||
self.maskCircleLayer.isHidden = false
|
|
||||||
self.maskProgressLayer.isHidden = true
|
|
||||||
self.maskGradientLayer.isHidden = false
|
|
||||||
CATransaction.commit()
|
|
||||||
|
|
||||||
completion()
|
|
||||||
|
|
||||||
self.updateGlowAndGradientAnimations(active: active, previousActive: nil)
|
|
||||||
|
|
||||||
self.maskBlobView.isHidden = false
|
|
||||||
self.maskBlobView.startAnimating()
|
|
||||||
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
|
||||||
|
|
||||||
self.updatedActive?(true)
|
|
||||||
|
|
||||||
CATransaction.begin()
|
|
||||||
let shrinkAnimation = CABasicAnimation(keyPath: "transform.scale")
|
|
||||||
shrinkAnimation.fromValue = 1.0
|
|
||||||
shrinkAnimation.toValue = 0.0
|
|
||||||
shrinkAnimation.duration = 0.15
|
|
||||||
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
|
||||||
|
|
||||||
CATransaction.setCompletionBlock {
|
|
||||||
CATransaction.begin()
|
CATransaction.begin()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
self.foregroundCircleLayer.isHidden = true
|
self.foregroundCircleLayer.isHidden = false
|
||||||
|
self.maskCircleLayer.isHidden = false
|
||||||
|
self.maskProgressLayer.isHidden = true
|
||||||
|
self.maskGradientLayer.isHidden = false
|
||||||
|
CATransaction.commit()
|
||||||
|
|
||||||
|
completion()
|
||||||
|
|
||||||
|
self.updateGlowAndGradientAnimations(active: active, previousActive: nil)
|
||||||
|
|
||||||
|
self.maskBlobView.isHidden = false
|
||||||
|
self.maskBlobView.startAnimating()
|
||||||
|
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||||
|
|
||||||
|
self.updatedActive?(true)
|
||||||
|
|
||||||
|
CATransaction.begin()
|
||||||
|
let shrinkAnimation = CABasicAnimation(keyPath: "transform.scale")
|
||||||
|
shrinkAnimation.fromValue = 1.0
|
||||||
|
shrinkAnimation.toValue = 0.0
|
||||||
|
shrinkAnimation.duration = 0.15
|
||||||
|
shrinkAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||||
|
|
||||||
|
CATransaction.setCompletionBlock {
|
||||||
|
if case .blob = self.state {
|
||||||
|
CATransaction.begin()
|
||||||
|
CATransaction.setDisableActions(true)
|
||||||
|
self.foregroundCircleLayer.isHidden = true
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.foregroundCircleLayer.add(shrinkAnimation, forKey: "insideShrink")
|
|
||||||
CATransaction.commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maskProgressLayer.add(groupAnimation, forKey: "progressCompletion")
|
self.maskProgressLayer.add(groupAnimation, forKey: "progressCompletion")
|
||||||
|
@ -3,8 +3,11 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
|
private let circleDiameter: CGFloat = 80.0
|
||||||
|
|
||||||
final class IconButtonNode: HighlightTrackingButtonNode {
|
final class IconButtonNode: HighlightTrackingButtonNode {
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
|
private var circleNode: ASImageNode?
|
||||||
|
|
||||||
var icon: UIImage? {
|
var icon: UIImage? {
|
||||||
didSet {
|
didSet {
|
||||||
@ -14,12 +17,44 @@ final class IconButtonNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var circleColor: UIColor? {
|
||||||
|
didSet {
|
||||||
|
if let color = self.circleColor {
|
||||||
|
let circleNode: ASImageNode
|
||||||
|
if let current = self.circleNode {
|
||||||
|
circleNode = current
|
||||||
|
} else {
|
||||||
|
circleNode = ASImageNode()
|
||||||
|
circleNode.alpha = 0.0
|
||||||
|
circleNode.displaysAsynchronously = false
|
||||||
|
circleNode.displayWithoutProcessing = true
|
||||||
|
circleNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: circleDiameter, height: circleDiameter))
|
||||||
|
circleNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)
|
||||||
|
self.insertSubnode(circleNode, at: 0)
|
||||||
|
self.circleNode = circleNode
|
||||||
|
}
|
||||||
|
circleNode.image = generateFilledCircleImage(diameter: circleDiameter, color: color)
|
||||||
|
} else if let current = self.circleNode {
|
||||||
|
self.circleNode = nil
|
||||||
|
current.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var isEnabled: Bool {
|
override var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
self.alpha = self.isEnabled ? 1.0 : 0.4
|
self.alpha = self.isEnabled ? 1.0 : 0.4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isPressing = false {
|
||||||
|
didSet {
|
||||||
|
if self.isPressing != oldValue && !self.isPressing {
|
||||||
|
self.highligthedChanged(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.isLayerBacked = true
|
self.iconNode.isLayerBacked = true
|
||||||
@ -33,11 +68,17 @@ final class IconButtonNode: HighlightTrackingButtonNode {
|
|||||||
self.highligthedChanged = { [weak self] highlighted in
|
self.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.09, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .linear)
|
||||||
transition.updateSublayerTransformScale(node: strongSelf, scale: 0.8)
|
transition.updateSublayerTransformScale(node: strongSelf, scale: 0.8)
|
||||||
} else {
|
if let circleNode = strongSelf.circleNode {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .spring)
|
transition.updateAlpha(node: circleNode, alpha: 1.0)
|
||||||
|
}
|
||||||
|
} else if !strongSelf.isPressing {
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .linear)
|
||||||
transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0)
|
transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0)
|
||||||
|
if let circleNode = strongSelf.circleNode {
|
||||||
|
transition.updateAlpha(node: circleNode, alpha: 0.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,5 +92,6 @@ final class IconButtonNode: HighlightTrackingButtonNode {
|
|||||||
if let image = self.iconNode.image {
|
if let image = self.iconNode.image {
|
||||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
|
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
|
||||||
}
|
}
|
||||||
|
self.circleNode?.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,11 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}, requestSearchByArtist: { [weak self] artist in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.context.sharedContext.openSearch(filter: .music, query: artist)
|
||||||
|
strongSelf.dismiss()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.ready.set(self.controllerNode.ready.get())
|
self.ready.set(self.controllerNode.ready.get())
|
||||||
|
@ -20,6 +20,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
private let type: MediaManagerPlayerType
|
private let type: MediaManagerPlayerType
|
||||||
private let requestDismiss: () -> Void
|
private let requestDismiss: () -> Void
|
||||||
private let requestShare: (MessageId) -> Void
|
private let requestShare: (MessageId) -> Void
|
||||||
|
private let requestSearchByArtist: (String) -> Void
|
||||||
private let playlistLocation: SharedMediaPlaylistLocation?
|
private let playlistLocation: SharedMediaPlaylistLocation?
|
||||||
private let isGlobalSearch: Bool
|
private let isGlobalSearch: Bool
|
||||||
|
|
||||||
@ -42,13 +43,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
private let replacementHistoryNodeReadyDisposable = MetaDisposable()
|
private let replacementHistoryNodeReadyDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void) {
|
init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void, requestSearchByArtist: @escaping (String) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.type = type
|
self.type = type
|
||||||
self.requestDismiss = requestDismiss
|
self.requestDismiss = requestDismiss
|
||||||
self.requestShare = requestShare
|
self.requestShare = requestShare
|
||||||
|
self.requestSearchByArtist = requestSearchByArtist
|
||||||
self.playlistLocation = playlistLocation
|
self.playlistLocation = playlistLocation
|
||||||
|
|
||||||
if case .regular = initialOrder {
|
if case .regular = initialOrder {
|
||||||
@ -224,6 +226,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
self?.requestShare(messageId)
|
self?.requestShare(messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.controlsNode.requestSearchByArtist = { [weak self] artist in
|
||||||
|
self?.requestSearchByArtist(artist)
|
||||||
|
}
|
||||||
|
|
||||||
self.controlsNode.updateOrder = { [weak self] order in
|
self.controlsNode.updateOrder = { [weak self] order in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let reversed: Bool
|
let reversed: Bool
|
||||||
|
@ -57,12 +57,13 @@ private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
|
|||||||
return size.width
|
return size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
private let titleFont = Font.semibold(17.0)
|
private let titleFont = Font.semibold(18.0)
|
||||||
private let descriptionFont = Font.regular(17.0)
|
private let descriptionFont = Font.regular(18.0)
|
||||||
|
|
||||||
private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, presentationData: PresentationData) -> (NSAttributedString?, NSAttributedString?) {
|
private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, presentationData: PresentationData) -> (NSAttributedString?, NSAttributedString?, Bool) {
|
||||||
var titleString: NSAttributedString?
|
var titleString: NSAttributedString?
|
||||||
var descriptionString: NSAttributedString?
|
var descriptionString: NSAttributedString?
|
||||||
|
var hasArtist = false
|
||||||
|
|
||||||
if let data = data {
|
if let data = data {
|
||||||
let titleText: String
|
let titleText: String
|
||||||
@ -71,6 +72,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres
|
|||||||
case let .music(title, performer, _, _):
|
case let .music(title, performer, _, _):
|
||||||
titleText = title ?? presentationData.strings.MediaPlayer_UnknownTrack
|
titleText = title ?? presentationData.strings.MediaPlayer_UnknownTrack
|
||||||
subtitleText = performer ?? presentationData.strings.MediaPlayer_UnknownArtist
|
subtitleText = performer ?? presentationData.strings.MediaPlayer_UnknownArtist
|
||||||
|
hasArtist = performer != nil
|
||||||
case .voice, .instantVideo:
|
case .voice, .instantVideo:
|
||||||
titleText = ""
|
titleText = ""
|
||||||
subtitleText = ""
|
subtitleText = ""
|
||||||
@ -80,7 +82,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres
|
|||||||
descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: presentationData.theme.list.itemSecondaryTextColor)
|
descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (titleString, descriptionString)
|
return (titleString, descriptionString, hasArtist)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class OverlayPlayerControlsNode: ASDisplayNode {
|
final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||||
@ -97,6 +99,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let descriptionNode: TextNode
|
private let descriptionNode: TextNode
|
||||||
private let shareNode: HighlightableButtonNode
|
private let shareNode: HighlightableButtonNode
|
||||||
|
private let artistButton: HighlightTrackingButtonNode
|
||||||
|
|
||||||
private let scrubberNode: MediaPlayerScrubbingNode
|
private let scrubberNode: MediaPlayerScrubbingNode
|
||||||
private let leftDurationLabel: MediaPlayerTimeTextNode
|
private let leftDurationLabel: MediaPlayerTimeTextNode
|
||||||
@ -105,6 +108,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
private let backwardButton: IconButtonNode
|
private let backwardButton: IconButtonNode
|
||||||
private let forwardButton: IconButtonNode
|
private let forwardButton: IconButtonNode
|
||||||
|
|
||||||
|
private var seekTimer: SwiftSignalKit.Timer?
|
||||||
|
private var seekRate: AudioPlaybackRate = .x2
|
||||||
|
private var previousRate: AudioPlaybackRate?
|
||||||
|
|
||||||
private var currentIsPaused: Bool?
|
private var currentIsPaused: Bool?
|
||||||
private let playPauseButton: IconButtonNode
|
private let playPauseButton: IconButtonNode
|
||||||
|
|
||||||
@ -124,6 +131,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
|
|
||||||
var requestCollapse: (() -> Void)?
|
var requestCollapse: (() -> Void)?
|
||||||
var requestShare: ((MessageId) -> Void)?
|
var requestShare: ((MessageId) -> Void)?
|
||||||
|
var requestSearchByArtist: ((String) -> Void)?
|
||||||
|
|
||||||
var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)?
|
var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)?
|
||||||
var control: ((SharedMediaPlayerControlAction) -> Void)?
|
var control: ((SharedMediaPlayerControlAction) -> Void)?
|
||||||
@ -167,6 +175,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
self.descriptionNode.isUserInteractionEnabled = false
|
self.descriptionNode.isUserInteractionEnabled = false
|
||||||
self.descriptionNode.displaysAsynchronously = false
|
self.descriptionNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.artistButton = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
self.shareNode = HighlightableButtonNode()
|
self.shareNode = HighlightableButtonNode()
|
||||||
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
|
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
|
||||||
|
|
||||||
@ -215,6 +225,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.albumArtNode)
|
self.addSubnode(self.albumArtNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.descriptionNode)
|
self.addSubnode(self.descriptionNode)
|
||||||
|
self.addSubnode(self.artistButton)
|
||||||
self.addSubnode(self.shareNode)
|
self.addSubnode(self.shareNode)
|
||||||
|
|
||||||
self.addSubnode(self.leftDurationLabel)
|
self.addSubnode(self.leftDurationLabel)
|
||||||
@ -400,6 +411,23 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside)
|
self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside)
|
||||||
self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), forControlEvents: .touchUpInside)
|
self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), forControlEvents: .touchUpInside)
|
||||||
self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside)
|
self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.artistButton.addTarget(self, action: #selector(self.artistPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
|
self.artistButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
strongSelf.descriptionNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
strongSelf.descriptionNode.alpha = 0.4
|
||||||
|
} else {
|
||||||
|
strongSelf.descriptionNode.alpha = 1.0
|
||||||
|
strongSelf.descriptionNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playPauseButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
|
||||||
|
self.backwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
|
||||||
|
self.forwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -411,6 +439,80 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
self.albumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:))))
|
self.albumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:))))
|
||||||
|
|
||||||
|
let backwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekForwardLongPress(_:)))
|
||||||
|
backwardLongPressGestureRecognizer.minimumPressDuration = 0.3
|
||||||
|
self.backwardButton.view.addGestureRecognizer(backwardLongPressGestureRecognizer)
|
||||||
|
|
||||||
|
let forwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekForwardLongPress(_:)))
|
||||||
|
forwardLongPressGestureRecognizer.minimumPressDuration = 0.3
|
||||||
|
self.forwardButton.view.addGestureRecognizer(forwardLongPressGestureRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func seekBackwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||||
|
switch gestureRecognizer.state {
|
||||||
|
case .began:
|
||||||
|
self.backwardButton.isPressing = true
|
||||||
|
self.previousRate = self.currentRate
|
||||||
|
self.seekRate = .x4
|
||||||
|
self.control?(.setBaseRate(self.seekRate))
|
||||||
|
let seekTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: true, completion: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if strongSelf.seekRate == .x4 {
|
||||||
|
strongSelf.seekRate = .x8
|
||||||
|
} else if strongSelf.seekRate == .x8 {
|
||||||
|
strongSelf.seekRate = .x16
|
||||||
|
}
|
||||||
|
strongSelf.control?(.setBaseRate(strongSelf.seekRate))
|
||||||
|
if strongSelf.seekRate == .x16 {
|
||||||
|
strongSelf.seekTimer?.invalidate()
|
||||||
|
strongSelf.seekTimer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.seekTimer = seekTimer
|
||||||
|
seekTimer.start()
|
||||||
|
case .ended, .cancelled:
|
||||||
|
self.backwardButton.isPressing = false
|
||||||
|
self.control?(.setBaseRate(self.previousRate ?? .x1))
|
||||||
|
self.seekTimer?.invalidate()
|
||||||
|
self.seekTimer = nil
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func seekForwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||||
|
switch gestureRecognizer.state {
|
||||||
|
case .began:
|
||||||
|
self.forwardButton.isPressing = true
|
||||||
|
self.previousRate = self.currentRate
|
||||||
|
self.seekRate = .x4
|
||||||
|
self.control?(.setBaseRate(self.seekRate))
|
||||||
|
let seekTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: true, completion: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if strongSelf.seekRate == .x4 {
|
||||||
|
strongSelf.seekRate = .x8
|
||||||
|
} else if strongSelf.seekRate == .x8 {
|
||||||
|
strongSelf.seekRate = .x16
|
||||||
|
}
|
||||||
|
strongSelf.control?(.setBaseRate(strongSelf.seekRate))
|
||||||
|
if strongSelf.seekRate == .x16 {
|
||||||
|
strongSelf.seekTimer?.invalidate()
|
||||||
|
strongSelf.seekTimer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.seekTimer = seekTimer
|
||||||
|
seekTimer.start()
|
||||||
|
case .ended, .cancelled:
|
||||||
|
self.forwardButton.isPressing = false
|
||||||
|
self.control?(.setBaseRate(self.previousRate ?? .x1))
|
||||||
|
self.seekTimer?.invalidate()
|
||||||
|
self.seekTimer = nil
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
@ -419,6 +521,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.playPauseButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
|
||||||
|
self.backwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
|
||||||
|
self.forwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
|
||||||
|
|
||||||
self.backgroundNode.image = generateBackground(theme: presentationData.theme)
|
self.backgroundNode.image = generateBackground(theme: presentationData.theme)
|
||||||
self.collapseNode.setImage(generateCollapseIcon(theme: presentationData.theme), for: [])
|
self.collapseNode.setImage(generateCollapseIcon(theme: presentationData.theme), for: [])
|
||||||
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
|
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
|
||||||
@ -456,7 +562,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
|
|
||||||
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
|
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
|
||||||
|
|
||||||
let (titleString, descriptionString) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
|
let (titleString, descriptionString, hasArtist) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
|
||||||
|
self.artistButton.isUserInteractionEnabled = hasArtist
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||||
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||||
@ -465,9 +572,12 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size))
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size))
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
|
||||||
transition.updateFrame(node: self.descriptionNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 26.0), size: descriptionLayout.size))
|
let descriptionFrame = CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 24.0), size: descriptionLayout.size)
|
||||||
|
transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame)
|
||||||
let _ = descriptionApply()
|
let _ = descriptionApply()
|
||||||
|
|
||||||
|
self.artistButton.frame = descriptionFrame.insetBy(dx: -8.0, dy: -8.0)
|
||||||
|
|
||||||
var albumArt: SharedMediaPlaybackAlbumArt?
|
var albumArt: SharedMediaPlaybackAlbumArt?
|
||||||
if let displayData = self.displayData {
|
if let displayData = self.displayData {
|
||||||
switch displayData {
|
switch displayData {
|
||||||
@ -753,6 +863,13 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func artistPressed() {
|
||||||
|
let (_, descriptionString, _) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
|
||||||
|
if let artist = descriptionString?.string {
|
||||||
|
self.requestSearchByArtist?(artist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
let result = super.hitTest(point, with: event)
|
let result = super.hitTest(point, with: event)
|
||||||
if result == self.view {
|
if result == self.view {
|
||||||
|
@ -219,6 +219,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||||||
nextRate = .x2
|
nextRate = .x2
|
||||||
case .x2:
|
case .x2:
|
||||||
nextRate = .x1
|
nextRate = .x1
|
||||||
|
default:
|
||||||
|
nextRate = .x1
|
||||||
}
|
}
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||||
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
||||||
|
@ -950,6 +950,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func openSearch(filter: ChatListSearchFilter, query: String?) {
|
||||||
|
if let rootController = self.mainWindow?.viewController as? TelegramRootController {
|
||||||
|
rootController.openChatsController(activateSearch: true, filter: filter, query: query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) {
|
public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) {
|
||||||
self.navigateToChatImpl(accountId, peerId, messageId)
|
self.navigateToChatImpl(accountId, peerId, messageId)
|
||||||
}
|
}
|
||||||
|
@ -227,6 +227,8 @@ final class SharedMediaPlayer {
|
|||||||
rateValue = 1.0
|
rateValue = 1.0
|
||||||
case .x2:
|
case .x2:
|
||||||
rateValue = 1.8
|
rateValue = 1.8
|
||||||
|
default:
|
||||||
|
rateValue = 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,6 +448,12 @@ final class SharedMediaPlayer {
|
|||||||
rateValue = 1.0
|
rateValue = 1.0
|
||||||
case .x2:
|
case .x2:
|
||||||
rateValue = 1.8
|
rateValue = 1.8
|
||||||
|
case .x4:
|
||||||
|
rateValue = 4.0
|
||||||
|
case .x8:
|
||||||
|
rateValue = 8.0
|
||||||
|
case .x16:
|
||||||
|
rateValue = 16.0
|
||||||
}
|
}
|
||||||
switch playbackItem {
|
switch playbackItem {
|
||||||
case let .audio(player):
|
case let .audio(player):
|
||||||
|
@ -144,7 +144,7 @@ public final class TelegramRootController: NavigationController {
|
|||||||
rootTabController.setControllers(controllers, selectedIndex: nil)
|
rootTabController.setControllers(controllers, selectedIndex: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func openChatsController(activateSearch: Bool) {
|
public func openChatsController(activateSearch: Bool, filter: ChatListSearchFilter = .chats, query: String? = nil) {
|
||||||
guard let rootTabController = self.rootTabController else {
|
guard let rootTabController = self.rootTabController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ public final class TelegramRootController: NavigationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if activateSearch {
|
if activateSearch {
|
||||||
self.chatListController?.activateSearch()
|
self.chatListController?.activateSearch(filter: filter, query: query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ public enum MusicPlaybackSettingsLooping: Int32 {
|
|||||||
public enum AudioPlaybackRate: Int32 {
|
public enum AudioPlaybackRate: Int32 {
|
||||||
case x1 = 1000
|
case x1 = 1000
|
||||||
case x2 = 2000
|
case x2 = 2000
|
||||||
|
case x4 = 4000
|
||||||
|
case x8 = 8000
|
||||||
|
case x16 = 16000
|
||||||
|
|
||||||
var doubleValue: Double {
|
var doubleValue: Double {
|
||||||
return Double(self.rawValue) / 1000.0
|
return Double(self.rawValue) / 1000.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user