mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Fixes
This commit is contained in:
parent
80e00293b6
commit
c187f16728
@ -40,8 +40,9 @@ public final class PeerSelectionControllerParams {
|
||||
public let createNewGroup: (() -> Void)?
|
||||
public let pretendPresentedInModal: Bool
|
||||
public let multipleSelection: Bool
|
||||
public let forwardedMessagesCount: Int
|
||||
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false) {
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessagesCount: Int = 0) {
|
||||
self.context = context
|
||||
self.filter = filter
|
||||
self.hasChatListSelector = hasChatListSelector
|
||||
@ -52,6 +53,7 @@ public final class PeerSelectionControllerParams {
|
||||
self.createNewGroup = createNewGroup
|
||||
self.pretendPresentedInModal = pretendPresentedInModal
|
||||
self.multipleSelection = multipleSelection
|
||||
self.forwardedMessagesCount = forwardedMessagesCount
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1727,32 +1727,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, displayBackground: true)
|
||||
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo
|
||||
mediaAccessoryPanel.getController = { [weak self] in
|
||||
return self?.navigationController?.topViewController as? ViewController
|
||||
}
|
||||
mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in
|
||||
(self?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(c)
|
||||
}
|
||||
mediaAccessoryPanel.close = { [weak self] in
|
||||
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
|
||||
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.toggleRate = {
|
||||
[weak self] in
|
||||
mediaAccessoryPanel.setRate = { [weak self] rate in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in
|
||||
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings
|
||||
|
||||
let nextRate: AudioPlaybackRate
|
||||
switch settings.voicePlaybackRate {
|
||||
case .x1:
|
||||
nextRate = .x2
|
||||
case .x2:
|
||||
nextRate = .x1
|
||||
default:
|
||||
nextRate = .x1
|
||||
}
|
||||
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
||||
return settings.withUpdatedVoicePlaybackRate(rate)
|
||||
})
|
||||
return nextRate
|
||||
return rate
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { baseRate in
|
||||
guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else {
|
||||
@ -1771,22 +1767,31 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
})
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let slowdown = baseRate == .x1
|
||||
controller.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
slowdown: slowdown,
|
||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
let slowdown: Bool?
|
||||
if baseRate == .x1 {
|
||||
slowdown = true
|
||||
} else if baseRate == .x2 {
|
||||
slowdown = false
|
||||
} else {
|
||||
slowdown = nil
|
||||
}
|
||||
if let slowdown = slowdown {
|
||||
controller.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
slowdown: slowdown,
|
||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { action in
|
||||
return true
|
||||
}
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { action in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -524,6 +524,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let controlsVisiblePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
|
||||
private let isShowingContextMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let hasExpandedCaptionPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private var hideControlsDisposable: Disposable?
|
||||
|
||||
var playbackCompleted: (() -> Void)?
|
||||
@ -701,12 +702,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.titleContentView = GalleryTitleView(frame: CGRect())
|
||||
self._titleView.set(.single(self.titleContentView))
|
||||
|
||||
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get())
|
||||
|> mapToSignal { isPlaying, isIntracting, controlsVisible, isShowingContextMenu -> Signal<Void, NoError> in
|
||||
if isShowingContextMenu {
|
||||
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get(), self.hasExpandedCaptionPromise.get())
|
||||
|> mapToSignal { isPlaying, isInteracting, controlsVisible, isShowingContextMenu, hasExpandedCaptionPromise -> Signal<Void, NoError> in
|
||||
if isShowingContextMenu || hasExpandedCaptionPromise {
|
||||
return .complete()
|
||||
}
|
||||
if isPlaying && !isIntracting && controlsVisible {
|
||||
if isPlaying && !isInteracting && controlsVisible {
|
||||
return .single(Void())
|
||||
|> delay(4.0, queue: Queue.mainQueue())
|
||||
} else {
|
||||
|
@ -76,6 +76,6 @@ typedef enum {
|
||||
|
||||
+ (UIInterfaceOrientation)_interfaceOrientationForDeviceOrientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
+ (bool)useLegacyCamera;
|
||||
+ (UIImage *)startImage;
|
||||
|
||||
@end
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#import <LegacyComponents/TGMenuSheetView.h>
|
||||
#import "TGAttachmentMenuCell.h"
|
||||
#import "TGCameraController.h"
|
||||
|
||||
#import <LegacyComponents/PGCamera.h>
|
||||
#import <LegacyComponents/TGCameraPreviewView.h>
|
||||
@ -46,6 +47,8 @@
|
||||
_camera = camera;
|
||||
|
||||
_previewView = [[TGCameraPreviewView alloc] initWithFrame:CGRectMake(0, 0, 84.0f, 84.0f)];
|
||||
[_previewView fadeInAnimated:false];
|
||||
[_previewView beginTransitionWithSnapshotImage:[TGCameraController startImage] animated:false];
|
||||
[_wrapperView addSubview:_previewView];
|
||||
[camera attachPreviewView:_previewView];
|
||||
|
||||
|
@ -2251,6 +2251,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
_dismissing = true;
|
||||
self.view.userInteractionEnabled = false;
|
||||
|
||||
|
||||
_focusControl.active = false;
|
||||
_rectangleView.hidden = true;
|
||||
|
||||
@ -2267,6 +2268,15 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
referenceFrame = self.beginTransitionOut();
|
||||
|
||||
__weak TGCameraController *weakSelf = self;
|
||||
[_camera captureNextFrameCompletion:^(UIImage *frameImage) {
|
||||
CGFloat minSize = MIN(frameImage.size.width, frameImage.size.height);
|
||||
CGFloat maxSize = MAX(frameImage.size.width, frameImage.size.height);
|
||||
|
||||
UIImage *image = TGPhotoEditorCrop(frameImage, nil, UIImageOrientationUp, 0.0f, CGRectMake((maxSize - minSize) / 2.0f, 0.0f, minSize, minSize), false, CGSizeMake(240.0f, 240.0f), frameImage.size, true);
|
||||
|
||||
UIImage *startImage = TGSecretBlurredAttachmentImage(image, image.size, NULL, false, 0);
|
||||
[TGCameraController saveStartImage:startImage];
|
||||
}];
|
||||
if (_standalone)
|
||||
{
|
||||
[self simpleTransitionOutWithVelocity:velocity completion:^
|
||||
@ -2757,11 +2767,6 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
}
|
||||
}
|
||||
|
||||
+ (bool)useLegacyCamera
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id<TGMediaSelectableItem>)currentItem storeAssets:(bool)storeAssets saveEditedPhotos:(bool)saveEditedPhotos descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator
|
||||
{
|
||||
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
||||
@ -3144,4 +3149,24 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
return value;
|
||||
}
|
||||
|
||||
#pragma mark - Start Image
|
||||
|
||||
static UIImage *startImage = nil;
|
||||
|
||||
+ (UIImage *)startImage
|
||||
{
|
||||
if (startImage == nil)
|
||||
startImage = TGComponentsImageNamed (@"VideoMessagePlaceholder.jpg");
|
||||
|
||||
return startImage;
|
||||
}
|
||||
|
||||
+ (void)saveStartImage:(UIImage *)image
|
||||
{
|
||||
if (image == nil)
|
||||
return;
|
||||
|
||||
startImage = image;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -129,10 +129,15 @@
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
if (resume)
|
||||
if (resume) {
|
||||
[strongSelf endResetTransitionAnimated:true];
|
||||
else
|
||||
[strongSelf fadeInAnimated:true];
|
||||
} else {
|
||||
if (strongSelf->_snapshotView != nil) {
|
||||
[strongSelf endTransitionAnimated:true];
|
||||
} else {
|
||||
[strongSelf fadeInAnimated:true];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
camera.captureStopped = ^(bool pause)
|
||||
@ -262,6 +267,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)hasTransitionSnapshot {
|
||||
return _snapshotView != nil;
|
||||
}
|
||||
|
||||
- (void)beginResetTransitionAnimated:(bool)animated
|
||||
{
|
||||
if (iosMajorVersion() < 7)
|
||||
@ -319,7 +328,12 @@
|
||||
|
||||
if (_snapshotView != nil)
|
||||
{
|
||||
CGSize size = TGScaleToFill(_snapshotView.frame.size, _wrapperView.frame.size);
|
||||
CGSize imageSize = _snapshotView.frame.size;
|
||||
if ([_snapshotView isKindOfClass:[UIImageView class]]) {
|
||||
imageSize = ((UIImageView *)_snapshotView).image.size;
|
||||
}
|
||||
|
||||
CGSize size = TGScaleToFill(imageSize, _wrapperView.frame.size);
|
||||
_snapshotView.frame = CGRectMake(floor((self.frame.size.width - size.width) / 2.0f), floor((self.frame.size.height - size.height) / 2.0f), size.width, size.height);
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@
|
||||
_hasSearchButton = hasSearchButton;
|
||||
_hasDeleteButton = hasDeleteButton;
|
||||
_hasViewButton = hasViewButton;
|
||||
_personalPhoto = ![TGCameraController useLegacyCamera] ? personalPhoto : false;
|
||||
_personalPhoto = personalPhoto;
|
||||
_isVideo = isVideo;
|
||||
_signup = signup;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ swift_library(
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/SegmentedControlNode:SegmentedControlNode",
|
||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -10,6 +10,7 @@ import TelegramStringFormatting
|
||||
import SelectablePeerNode
|
||||
import PeerPresenceStatusManager
|
||||
import AccountContext
|
||||
import ShimmerEffect
|
||||
|
||||
final class ShareControllerInteraction {
|
||||
var foundPeers: [RenderedPeer] = []
|
||||
@ -89,14 +90,14 @@ final class ShareControllerPeerGridItem: GridItem {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peer: RenderedPeer
|
||||
let peer: RenderedPeer?
|
||||
let presence: PeerPresence?
|
||||
let controllerInteraction: ShareControllerInteraction
|
||||
let search: Bool
|
||||
|
||||
let section: GridSection?
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer, presence: PeerPresence?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) {
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer?, presence: PeerPresence?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -130,12 +131,15 @@ final class ShareControllerPeerGridItem: GridItem {
|
||||
}
|
||||
|
||||
final class ShareControllerPeerGridItemNode: GridItemNode {
|
||||
private var currentState: (AccountContext, PresentationTheme, PresentationStrings, RenderedPeer, Bool, PeerPresence?)?
|
||||
private var currentState: (AccountContext, PresentationTheme, PresentationStrings, RenderedPeer?, Bool, PeerPresence?)?
|
||||
private let peerNode: SelectablePeerNode
|
||||
private var presenceManager: PeerPresenceStatusManager?
|
||||
|
||||
var controllerInteraction: ShareControllerInteraction?
|
||||
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
|
||||
override init() {
|
||||
self.peerNode = SelectablePeerNode()
|
||||
|
||||
@ -143,7 +147,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
||||
|
||||
self.peerNode.toggleSelection = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let (_, _, _, peer, search, _) = strongSelf.currentState {
|
||||
if let (_, _, _, maybePeer, search, _) = strongSelf.currentState, let peer = maybePeer {
|
||||
if let _ = peer.peers[peer.peerId] {
|
||||
strongSelf.controllerInteraction?.togglePeer(peer, search)
|
||||
}
|
||||
@ -159,13 +163,21 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
||||
})
|
||||
}
|
||||
|
||||
func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer, presence: PeerPresence?, search: Bool, synchronousLoad: Bool, force: Bool) {
|
||||
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||
var rect = absoluteRect
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
if let shimmerNode = self.placeholderNode {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer?, presence: PeerPresence?, search: Bool, synchronousLoad: Bool, force: Bool) {
|
||||
if force || self.currentState == nil || self.currentState!.0 !== context || self.currentState!.3 != peer || !arePeerPresencesEqual(self.currentState!.5, presence) {
|
||||
let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: theme.chatList.secretTitleColor, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.checkContentColor, avatarPlaceholderColor: theme.list.mediaPlaceholderColor)
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
var online = false
|
||||
if let peer = peer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != context.account.peerId {
|
||||
if let peer = peer?.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != context.account.peerId {
|
||||
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: timestamp)
|
||||
if case .online = relativeStatus {
|
||||
online = true
|
||||
@ -173,7 +185,39 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
||||
}
|
||||
|
||||
self.peerNode.theme = itemTheme
|
||||
self.peerNode.setup(context: context, theme: theme, strings: strings, peer: EngineRenderedPeer(peer), online: online, synchronousLoad: synchronousLoad)
|
||||
if let peer = peer {
|
||||
self.peerNode.setup(context: context, theme: theme, strings: strings, peer: EngineRenderedPeer(peer), online: online, synchronousLoad: synchronousLoad)
|
||||
if let shimmerNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
shimmerNode.removeFromSupernode()
|
||||
}
|
||||
} else {
|
||||
let shimmerNode: ShimmerEffectNode
|
||||
if let current = self.placeholderNode {
|
||||
shimmerNode = current
|
||||
} else {
|
||||
shimmerNode = ShimmerEffectNode()
|
||||
self.placeholderNode = shimmerNode
|
||||
self.addSubnode(shimmerNode)
|
||||
}
|
||||
shimmerNode.frame = self.bounds
|
||||
if let (rect, size) = self.absoluteLocation {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: size)
|
||||
}
|
||||
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
|
||||
let titleLineWidth: CGFloat = 56.0
|
||||
let lineDiameter: CGFloat = 10.0
|
||||
|
||||
let iconFrame = CGRect(x: 13.0, y: 4.0, width: 60.0, height: 60.0)
|
||||
shapes.append(.circle(iconFrame))
|
||||
|
||||
let titleFrame = CGRect(x: 15.0, y: 70.0, width: 56.0, height: 10.0)
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter))
|
||||
|
||||
shimmerNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: self.bounds.size)
|
||||
}
|
||||
self.currentState = (context, theme, strings, peer, search, presence)
|
||||
self.setNeedsLayout()
|
||||
if let presence = presence as? TelegramUserPresence {
|
||||
@ -185,7 +229,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
||||
|
||||
func updateSelection(animated: Bool) {
|
||||
var selected = false
|
||||
if let controllerInteraction = self.controllerInteraction, let (_, _, _, peer, _, _) = self.currentState {
|
||||
if let controllerInteraction = self.controllerInteraction, let (_, _, _, maybePeer, _, _) = self.currentState, let peer = maybePeer {
|
||||
selected = controllerInteraction.selectedPeerIds.contains(peer.peerId)
|
||||
}
|
||||
|
||||
@ -197,5 +241,21 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
||||
|
||||
let bounds = self.bounds
|
||||
self.peerNode.frame = bounds
|
||||
self.placeholderNode?.frame = bounds
|
||||
|
||||
if let (_, theme, _, _, _, _) = self.currentState, let shimmerNode = self.placeholderNode {
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
|
||||
let titleLineWidth: CGFloat = 56.0
|
||||
let lineDiameter: CGFloat = 10.0
|
||||
|
||||
let iconFrame = CGRect(x: (bounds.width - 60.0) / 2.0, y: 4.0, width: 60.0, height: 60.0)
|
||||
shapes.append(.circle(iconFrame))
|
||||
|
||||
let titleFrame = CGRect(x: (bounds.width - titleLineWidth) / 2.0, y: 70.0, width: titleLineWidth, height: 10.0)
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter))
|
||||
|
||||
shimmerNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: self.bounds.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,13 +110,17 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable {
|
||||
|
||||
private struct ShareSearchPeerEntry: Comparable, Identifiable {
|
||||
let index: Int32
|
||||
let peer: RenderedPeer
|
||||
let peer: RenderedPeer?
|
||||
let presence: PeerPresence?
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
||||
var stableId: Int64 {
|
||||
return self.peer.peerId.toInt64()
|
||||
if let peer = self.peer {
|
||||
return peer.peerId.toInt64()
|
||||
} else {
|
||||
return Int64(index)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ShareSearchPeerEntry, rhs: ShareSearchPeerEntry) -> Bool {
|
||||
@ -134,7 +138,7 @@ private struct ShareSearchPeerEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
func item(context: AccountContext, interfaceInteraction: ShareControllerInteraction) -> GridItem {
|
||||
return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: peer, presence: self.presence, controllerInteraction: interfaceInteraction, search: true)
|
||||
return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, controllerInteraction: interfaceInteraction, search: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,10 +250,13 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
if !query.isEmpty {
|
||||
let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1)
|
||||
let foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased())
|
||||
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], []))
|
||||
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> = .single(([], [], true))
|
||||
|> then(
|
||||
context.engine.peers.searchPeers(query: query)
|
||||
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
|
||||
|> map { a, b -> ([FoundPeer], [FoundPeer], Bool) in
|
||||
return (a, b, false)
|
||||
}
|
||||
)
|
||||
|
||||
return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers)
|
||||
@ -278,21 +285,28 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
}
|
||||
}
|
||||
|
||||
for foundPeer in foundRemotePeers.0 {
|
||||
let peer = foundPeer.peer
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: foundPeer.peer), presence: nil, theme: theme, strings: strings))
|
||||
if foundRemotePeers.2 {
|
||||
for _ in 0 ..< 4 {
|
||||
entries.append(ShareSearchPeerEntry(index: index, peer: nil, presence: nil, theme: theme, strings: strings))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
for foundPeer in foundRemotePeers.1 {
|
||||
let peer = foundPeer.peer
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: peer), presence: nil, theme: theme, strings: strings))
|
||||
index += 1
|
||||
} else {
|
||||
for foundPeer in foundRemotePeers.0 {
|
||||
let peer = foundPeer.peer
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: foundPeer.peer), presence: nil, theme: theme, strings: strings))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
for foundPeer in foundRemotePeers.1 {
|
||||
let peer = foundPeer.peer
|
||||
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: peer), presence: nil, theme: theme, strings: strings))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,7 +450,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
var scrollToItem: GridNodeScrollToItem?
|
||||
if !self.contentGridNode.isHidden, let ensurePeerVisibleOnLayout = self.ensurePeerVisibleOnLayout {
|
||||
self.ensurePeerVisibleOnLayout = nil
|
||||
if let index = self.entries.firstIndex(where: { $0.peer.peerId == ensurePeerVisibleOnLayout }) {
|
||||
if let index = self.entries.firstIndex(where: { $0.peer?.peerId == ensurePeerVisibleOnLayout }) {
|
||||
scrollToItem = GridNodeScrollToItem(index: index, position: .visible, transition: transition, directionHint: .up, adjustForSection: false)
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public final class MediaNavigationAccessoryContainerNode: ASDisplayNode, UIGestu
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.headerNode = MediaNavigationAccessoryHeaderNode(presentationData: self.presentationData)
|
||||
self.headerNode = MediaNavigationAccessoryHeaderNode(context: context)
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -10,6 +10,7 @@ import UniversalMediaPlayer
|
||||
import AccountContext
|
||||
import TelegramStringFormatting
|
||||
import ManagedAnimationNode
|
||||
import ContextUI
|
||||
|
||||
private let titleFont = Font.regular(12.0)
|
||||
private let subtitleFont = Font.regular(10.0)
|
||||
@ -130,6 +131,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
public static let minimizedHeight: CGFloat = 37.0
|
||||
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
@ -148,7 +150,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
private let closeButton: HighlightableButtonNode
|
||||
private let actionButton: HighlightTrackingButtonNode
|
||||
private let playPauseIconNode: PlayPauseIconNode
|
||||
private let rateButton: HighlightableButtonNode
|
||||
private let rateButton: RateButton
|
||||
private let accessibilityAreaNode: AccessibilityAreaNode
|
||||
|
||||
private let scrubbingNode: MediaPlayerScrubbingNode
|
||||
@ -167,24 +169,31 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
|
||||
public var tapAction: (() -> Void)?
|
||||
public var close: (() -> Void)?
|
||||
public var toggleRate: (() -> Void)?
|
||||
public var setRate: ((AudioPlaybackRate) -> Void)?
|
||||
public var togglePlayPause: (() -> Void)?
|
||||
public var playPrevious: (() -> Void)?
|
||||
public var playNext: (() -> Void)?
|
||||
|
||||
public var getController: (() -> ViewController?)?
|
||||
public var presentInGlobalOverlay: ((ViewController) -> Void)?
|
||||
|
||||
public var playbackBaseRate: AudioPlaybackRate? = nil {
|
||||
didSet {
|
||||
guard self.playbackBaseRate != oldValue, let playbackBaseRate = self.playbackBaseRate else {
|
||||
return
|
||||
}
|
||||
switch playbackBaseRate {
|
||||
case .x0_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x1:
|
||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
|
||||
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
||||
case .x1_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x2:
|
||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "2x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
|
||||
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
||||
@ -208,7 +217,9 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
}
|
||||
}
|
||||
|
||||
public init(presentationData: PresentationData) {
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.theme = presentationData.theme
|
||||
self.strings = presentationData.strings
|
||||
self.dateTimeFormat = presentationData.dateTimeFormat
|
||||
@ -236,7 +247,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
self.closeButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 2.0)
|
||||
self.closeButton.displaysAsynchronously = false
|
||||
|
||||
self.rateButton = HighlightableButtonNode()
|
||||
self.rateButton = RateButton()
|
||||
|
||||
self.rateButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -4.0, bottom: -8.0, right: -4.0)
|
||||
self.rateButton.displaysAsynchronously = false
|
||||
@ -265,9 +276,6 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
self.scrollNode.addSubnode(self.previousItemNode)
|
||||
self.scrollNode.addSubnode(self.nextItemNode)
|
||||
|
||||
//self.addSubnode(self.leftMaskNode)
|
||||
//self.addSubnode(self.rightMaskNode)
|
||||
|
||||
self.addSubnode(self.closeButton)
|
||||
self.addSubnode(self.rateButton)
|
||||
self.addSubnode(self.accessibilityAreaNode)
|
||||
@ -276,9 +284,13 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
self.addSubnode(self.actionButton)
|
||||
|
||||
self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.rateButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
self?.openRateMenu(sourceNode: sourceNode, gesture: gesture)
|
||||
}
|
||||
|
||||
self.addSubnode(self.scrubbingNode)
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
@ -300,13 +312,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
return
|
||||
}
|
||||
if let status = status {
|
||||
let baseRate: AudioPlaybackRate
|
||||
if status.baseRate.isEqual(to: 1.0) {
|
||||
baseRate = .x1
|
||||
} else {
|
||||
baseRate = .x2
|
||||
}
|
||||
strongSelf.playbackBaseRate = baseRate
|
||||
strongSelf.playbackBaseRate = AudioPlaybackRate(status.baseRate)
|
||||
} else {
|
||||
strongSelf.playbackBaseRate = .x1
|
||||
}
|
||||
@ -365,10 +371,14 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
|
||||
if let playbackBaseRate = self.playbackBaseRate {
|
||||
switch playbackBaseRate {
|
||||
case .x0_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x1:
|
||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x1_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x2:
|
||||
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "2x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor)))
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -475,8 +485,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
|
||||
let rateButtonSize = CGSize(width: 24.0, height: minHeight)
|
||||
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 17.0 - rateButtonSize.width - rightInset, y: 0.0), size: rateButtonSize))
|
||||
let rateButtonSize = CGSize(width: 30.0, height: minHeight)
|
||||
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 20.0 - closeButtonSize.width - rateButtonSize.width - rightInset, y: -4.0), size: rateButtonSize))
|
||||
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0)))
|
||||
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
|
||||
transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 37.0 - 2.0), size: CGSize(width: size.width, height: 2.0)))
|
||||
@ -491,7 +501,48 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
}
|
||||
|
||||
@objc public func rateButtonPressed() {
|
||||
self.toggleRate?()
|
||||
self.rateButton.contextAction?(self.rateButton.containerNode, nil)
|
||||
}
|
||||
|
||||
private func speedList(strings: PresentationStrings) -> [(String, String, AudioPlaybackRate)] {
|
||||
let speedList: [(String, String, AudioPlaybackRate)] = [
|
||||
("0.5x", "0.5x", .x0_5),
|
||||
(strings.PlaybackSpeed_Normal, "1x", .x1),
|
||||
("1.5x", "1.5x", .x1_5),
|
||||
("2x", "2x", .x2)
|
||||
]
|
||||
return speedList
|
||||
}
|
||||
|
||||
private func contextMenuSpeedItems() -> Signal<[ContextMenuItem], NoError> {
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
for (text, _, rate) in self.speedList(strings: self.strings) {
|
||||
let isSelected = self.playbackBaseRate == rate
|
||||
items.append(.action(ContextMenuActionItem(text: text, icon: { theme in
|
||||
if isSelected {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
self?.setRate?(rate)
|
||||
})))
|
||||
}
|
||||
|
||||
return .single(items)
|
||||
}
|
||||
|
||||
private func openRateMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||
guard let controller = self.getController?() else {
|
||||
return
|
||||
}
|
||||
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems()
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode)), items: items, reactionItems: [], gesture: gesture)
|
||||
self.presentInGlobalOverlay?(contextController)
|
||||
// controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@objc public func actionButtonPressed() {
|
||||
@ -554,3 +605,159 @@ private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .white) -> UIImage? {
|
||||
return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: color) {
|
||||
image.draw(at: CGPoint(x: 0.0, y: 0.0))
|
||||
}
|
||||
|
||||
let string = NSMutableAttributedString(string: rate, font: Font.with(size: 11.0, design: .round, weight: .semibold), textColor: color)
|
||||
|
||||
var offset = CGPoint(x: 1.0, y: 0.0)
|
||||
if rate.count >= 3 {
|
||||
if rate == "0.5x" {
|
||||
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.5
|
||||
} else {
|
||||
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.3
|
||||
}
|
||||
} else {
|
||||
offset.x += -0.3
|
||||
}
|
||||
|
||||
if !isLarge {
|
||||
offset.x *= 0.5
|
||||
offset.y *= 0.5
|
||||
}
|
||||
|
||||
let boundingRect = string.boundingRect(with: size, options: [], context: nil)
|
||||
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0)))
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
}
|
||||
|
||||
private final class RateButton: HighlightableButtonNode {
|
||||
enum Content {
|
||||
case image(UIImage?)
|
||||
}
|
||||
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
private let wide: Bool
|
||||
|
||||
init(wide: Bool = false) {
|
||||
self.wide = wide
|
||||
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.contentMode = .scaleToFill
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
|
||||
if let image = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0)
|
||||
}
|
||||
|
||||
private var content: Content?
|
||||
func setContent(_ content: Content, animated: Bool = false) {
|
||||
if animated {
|
||||
if let snapshotView = self.referenceNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.referenceNode.frame
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
|
||||
|
||||
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3)
|
||||
}
|
||||
|
||||
switch content {
|
||||
case let .image(image):
|
||||
if let image = image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
}
|
||||
} else {
|
||||
self.content = content
|
||||
switch content {
|
||||
case let .image(image):
|
||||
if let image = image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.isOpaque = false
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: wide ? 32.0 : 22.0, height: 44.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -4,17 +4,21 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramUIPreferences
|
||||
|
||||
public final class MediaNavigationAccessoryPanel: ASDisplayNode {
|
||||
public let containerNode: MediaNavigationAccessoryContainerNode
|
||||
|
||||
public var close: (() -> Void)?
|
||||
public var toggleRate: (() -> Void)?
|
||||
public var setRate: ((AudioPlaybackRate) -> Void)?
|
||||
public var togglePlayPause: (() -> Void)?
|
||||
public var tapAction: (() -> Void)?
|
||||
public var playPrevious: (() -> Void)?
|
||||
public var playNext: (() -> Void)?
|
||||
|
||||
public var getController: (() -> ViewController?)?
|
||||
public var presentInGlobalOverlay: ((ViewController) -> Void)?
|
||||
|
||||
public init(context: AccountContext, displayBackground: Bool = false) {
|
||||
self.containerNode = MediaNavigationAccessoryContainerNode(context: context, displayBackground: displayBackground)
|
||||
|
||||
@ -27,8 +31,8 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
|
||||
close()
|
||||
}
|
||||
}
|
||||
self.containerNode.headerNode.toggleRate = { [weak self] in
|
||||
self?.toggleRate?()
|
||||
self.containerNode.headerNode.setRate = { [weak self] rate in
|
||||
self?.setRate?(rate)
|
||||
}
|
||||
self.containerNode.headerNode.togglePlayPause = { [weak self] in
|
||||
if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause {
|
||||
@ -50,6 +54,20 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
|
||||
playNext()
|
||||
}
|
||||
}
|
||||
|
||||
self.containerNode.headerNode.getController = { [weak self] in
|
||||
if let strongSelf = self, let getController = strongSelf.getController {
|
||||
return getController()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
self.containerNode.headerNode.presentInGlobalOverlay = { [weak self] c in
|
||||
if let strongSelf = self, let presentInGlobalOverlay = strongSelf.presentInGlobalOverlay {
|
||||
presentInGlobalOverlay(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -645,32 +645,28 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context)
|
||||
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo
|
||||
mediaAccessoryPanel.getController = { [weak self] in
|
||||
return self
|
||||
}
|
||||
mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in
|
||||
self?.presentInGlobalOverlay(c)
|
||||
}
|
||||
mediaAccessoryPanel.close = { [weak self] in
|
||||
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
|
||||
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.toggleRate = {
|
||||
[weak self] in
|
||||
mediaAccessoryPanel.setRate = { [weak self] rate in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in
|
||||
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings
|
||||
|
||||
let nextRate: AudioPlaybackRate
|
||||
switch settings.voicePlaybackRate {
|
||||
case .x1:
|
||||
nextRate = .x2
|
||||
case .x2:
|
||||
nextRate = .x1
|
||||
default:
|
||||
nextRate = .x1
|
||||
}
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
||||
return settings.withUpdatedVoicePlaybackRate(rate)
|
||||
})
|
||||
return nextRate
|
||||
return rate
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { baseRate in
|
||||
guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else {
|
||||
@ -688,22 +684,31 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
})
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let slowdown = baseRate == .x1
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
slowdown: slowdown,
|
||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
let slowdown: Bool?
|
||||
if baseRate == .x1 {
|
||||
slowdown = true
|
||||
} else if baseRate == .x2 {
|
||||
slowdown = false
|
||||
} else {
|
||||
slowdown = nil
|
||||
}
|
||||
if let slowdown = slowdown {
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
slowdown: slowdown,
|
||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { action in
|
||||
return true
|
||||
}
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { action in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
mediaAccessoryPanel.togglePlayPause = { [weak self] in
|
||||
|
@ -4776,7 +4776,6 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let speakingPeersUpdated = self.currentSpeakingPeers != speakingPeers
|
||||
self.currentCallMembers = callMembers
|
||||
self.currentSpeakingPeers = speakingPeers
|
||||
self.currentInvitedPeers = invitedPeers
|
||||
|
||||
var entries: [ListEntry] = []
|
||||
@ -5081,9 +5080,10 @@ public final class VoiceChatController: ViewController {
|
||||
self.updateMainVideo(waitForFullSize: true, entries: fullscreenEntries, force: true)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
self.updateRequestedVideoChannels()
|
||||
|
||||
self.currentSpeakingPeers = speakingPeers
|
||||
self.peerIdToEndpointId = peerIdToEndpointId
|
||||
|
||||
var updateLayout = false
|
||||
|
@ -10978,7 +10978,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: filter, attemptSelection: { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}, multipleSelection: true))
|
||||
}, multipleSelection: true, forwardedMessagesCount: messages.count))
|
||||
let context = self.context
|
||||
attemptSelectionImpl = { [weak controller] peer in
|
||||
guard let controller = controller else {
|
||||
|
@ -1080,7 +1080,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let viaBotNode = viaBotApply()
|
||||
if strongSelf.viaBotNode == nil {
|
||||
strongSelf.viaBotNode = viaBotNode
|
||||
strongSelf.addSubnode(viaBotNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
|
||||
}
|
||||
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size)
|
||||
viaBotNode.frame = viaBotFrame
|
||||
@ -1161,7 +1161,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
} else {
|
||||
let forwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper))
|
||||
strongSelf.forwardBackgroundNode = forwardBackgroundNode
|
||||
strongSelf.addSubnode(forwardBackgroundNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(forwardBackgroundNode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1169,7 +1169,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let forwardInfoNode = forwardInfoApply(forwardInfoSize.width)
|
||||
if strongSelf.forwardInfoNode == nil {
|
||||
strongSelf.forwardInfoNode = forwardInfoNode
|
||||
strongSelf.addSubnode(forwardInfoNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode)
|
||||
}
|
||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize)
|
||||
forwardInfoNode.frame = forwardInfoFrame
|
||||
|
@ -43,11 +43,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
private var forwardBackgroundNode: ASImageNode?
|
||||
private var forwardBackgroundNode: NavigationBackgroundNode?
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundNode: ASImageNode?
|
||||
private var replyBackgroundNode: NavigationBackgroundNode?
|
||||
|
||||
private var actionButtonsNode: ChatMessageActionButtonsNode?
|
||||
|
||||
@ -389,7 +389,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
||||
var updatedReplyBackgroundNode: ASImageNode?
|
||||
var updatedReplyBackgroundNode: NavigationBackgroundNode?
|
||||
var replyBackgroundImage: UIImage?
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
@ -432,6 +432,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
let botString = addAttributesToStringWithRanges(item.presentationData.strings.Conversation_MessageViaUser("@\(inlineBotNameString)")._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
|
||||
viaBotApply = viaBotLayout(TextNodeLayoutArguments(attributedString: botString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, availableWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
ignoreForward = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,11 +466,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if let currentReplyBackgroundNode = currentReplyBackgroundNode {
|
||||
updatedReplyBackgroundNode = currentReplyBackgroundNode
|
||||
} else {
|
||||
updatedReplyBackgroundNode = ASImageNode()
|
||||
updatedReplyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper))
|
||||
}
|
||||
|
||||
let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners)
|
||||
replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage
|
||||
updatedReplyBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate)
|
||||
}
|
||||
|
||||
var updatedShareButtonNode: ChatMessageShareButton?
|
||||
@ -487,7 +488,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
var forwardAuthorSignature: String?
|
||||
|
||||
var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode)?
|
||||
var updatedForwardBackgroundNode: ASImageNode?
|
||||
var updatedForwardBackgroundNode: NavigationBackgroundNode?
|
||||
var forwardBackgroundImage: UIImage?
|
||||
|
||||
if !ignoreForward, let forwardInfo = item.message.forwardInfo {
|
||||
@ -517,11 +518,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if let currentForwardBackgroundNode = currentForwardBackgroundNode {
|
||||
updatedForwardBackgroundNode = currentForwardBackgroundNode
|
||||
} else {
|
||||
updatedForwardBackgroundNode = ASImageNode()
|
||||
updatedForwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper))
|
||||
}
|
||||
|
||||
let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners)
|
||||
forwardBackgroundImage = graphics.chatServiceBubbleFillImage
|
||||
updatedForwardBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate)
|
||||
}
|
||||
|
||||
var maxContentWidth = normalDisplaySize.width
|
||||
@ -613,10 +613,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if let updatedReplyBackgroundNode = updatedReplyBackgroundNode {
|
||||
if strongSelf.replyBackgroundNode == nil {
|
||||
strongSelf.replyBackgroundNode = updatedReplyBackgroundNode
|
||||
strongSelf.addSubnode(updatedReplyBackgroundNode)
|
||||
updatedReplyBackgroundNode.image = replyBackgroundImage
|
||||
} else {
|
||||
strongSelf.replyBackgroundNode?.image = replyBackgroundImage
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(updatedReplyBackgroundNode)
|
||||
}
|
||||
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
||||
replyBackgroundNode.removeFromSupernode()
|
||||
@ -627,11 +624,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
let viaBotNode = viaBotApply()
|
||||
if strongSelf.viaBotNode == nil {
|
||||
strongSelf.viaBotNode = viaBotNode
|
||||
strongSelf.addSubnode(viaBotNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
|
||||
}
|
||||
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0), size: viaBotLayout.size)
|
||||
viaBotNode.frame = viaBotFrame
|
||||
strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0))
|
||||
let replyBackgroundFrame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0))
|
||||
strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame
|
||||
strongSelf.replyBackgroundNode?.update(size: replyBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate)
|
||||
} else if let viaBotNode = strongSelf.viaBotNode {
|
||||
viaBotNode.removeFromSupernode()
|
||||
strongSelf.viaBotNode = nil
|
||||
@ -641,7 +640,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
let replyInfoNode = replyInfoApply()
|
||||
if strongSelf.replyInfoNode == nil {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.addSubnode(replyInfoNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
}
|
||||
var viaBotSize = CGSize()
|
||||
if let viaBotNode = strongSelf.viaBotNode {
|
||||
@ -654,7 +653,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
replyInfoNode.frame = replyInfoFrame
|
||||
strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
|
||||
let replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
|
||||
strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame
|
||||
strongSelf.replyBackgroundNode?.update(size: replyBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate)
|
||||
} else if let replyInfoNode = strongSelf.replyInfoNode {
|
||||
replyInfoNode.removeFromSupernode()
|
||||
strongSelf.replyInfoNode = nil
|
||||
@ -694,8 +695,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if let updatedForwardBackgroundNode = updatedForwardBackgroundNode {
|
||||
if strongSelf.forwardBackgroundNode == nil {
|
||||
strongSelf.forwardBackgroundNode = updatedForwardBackgroundNode
|
||||
strongSelf.addSubnode(updatedForwardBackgroundNode)
|
||||
updatedForwardBackgroundNode.image = forwardBackgroundImage
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(updatedForwardBackgroundNode)
|
||||
}
|
||||
} else if let forwardBackgroundNode = strongSelf.forwardBackgroundNode {
|
||||
forwardBackgroundNode.removeFromSupernode()
|
||||
@ -706,7 +706,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
let forwardInfoNode = forwardInfoApply(forwardInfoSize.width)
|
||||
if strongSelf.forwardInfoNode == nil {
|
||||
strongSelf.forwardInfoNode = forwardInfoNode
|
||||
strongSelf.addSubnode(forwardInfoNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode)
|
||||
forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in
|
||||
guard let strongSelf = strongSelf, let item = strongSelf.item else {
|
||||
return
|
||||
@ -716,7 +716,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize)
|
||||
forwardInfoNode.frame = forwardInfoFrame
|
||||
strongSelf.forwardBackgroundNode?.frame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0))
|
||||
let forwardBackgroundFrame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0))
|
||||
strongSelf.forwardBackgroundNode?.frame = forwardBackgroundFrame
|
||||
strongSelf.forwardBackgroundNode?.update(size: forwardBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate)
|
||||
} else if let forwardInfoNode = strongSelf.forwardInfoNode {
|
||||
forwardInfoNode.removeFromSupernode()
|
||||
strongSelf.forwardInfoNode = nil
|
||||
|
@ -719,7 +719,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let viaBotNode = viaBotApply()
|
||||
if strongSelf.viaBotNode == nil {
|
||||
strongSelf.viaBotNode = viaBotNode
|
||||
strongSelf.addSubnode(viaBotNode)
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
|
||||
}
|
||||
viaBotNode.frame = viaBotFrame
|
||||
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
||||
|
@ -11,13 +11,15 @@ private final class InstantVideoRadialStatusNodeParameters: NSObject {
|
||||
let progress: CGFloat
|
||||
let dimProgress: CGFloat
|
||||
let playProgress: CGFloat
|
||||
let blinkProgress: CGFloat
|
||||
let hasSeek: Bool
|
||||
|
||||
init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat, hasSeek: Bool) {
|
||||
init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat, blinkProgress: CGFloat, hasSeek: Bool) {
|
||||
self.color = color
|
||||
self.progress = progress
|
||||
self.dimProgress = dimProgress
|
||||
self.playProgress = playProgress
|
||||
self.blinkProgress = blinkProgress
|
||||
self.hasSeek = hasSeek
|
||||
}
|
||||
}
|
||||
@ -64,6 +66,12 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
}
|
||||
}
|
||||
|
||||
private var effectiveBlinkProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var _statusValue: MediaPlayerStatus?
|
||||
private var statusValue: MediaPlayerStatus? {
|
||||
get {
|
||||
@ -196,8 +204,38 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
if let seekingProgress = self.seekingProgress {
|
||||
if seekingProgress > 0.98 && fraction > 0.0 && fraction < 0.05 {
|
||||
self.hapticFeedback.impact(.light)
|
||||
|
||||
let blinkAnimation = POPBasicAnimation()
|
||||
blinkAnimation.property = POPAnimatableProperty.property(withName: "blinkProgress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! InstantVideoRadialStatusNode).effectiveBlinkProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! InstantVideoRadialStatusNode).effectiveBlinkProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
blinkAnimation.fromValue = 1.0 as NSNumber
|
||||
blinkAnimation.toValue = 0.0 as NSNumber
|
||||
blinkAnimation.duration = 0.5
|
||||
self.pop_add(blinkAnimation, forKey: "blinkProgress")
|
||||
} else if seekingProgress > 0.0 && seekingProgress < 0.05 && fraction > 0.98 {
|
||||
self.hapticFeedback.impact(.light)
|
||||
|
||||
let blinkAnimation = POPBasicAnimation()
|
||||
blinkAnimation.property = POPAnimatableProperty.property(withName: "blinkProgress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! InstantVideoRadialStatusNode).effectiveBlinkProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! InstantVideoRadialStatusNode).effectiveBlinkProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
blinkAnimation.fromValue = -1.0 as NSNumber
|
||||
blinkAnimation.toValue = 0.0 as NSNumber
|
||||
blinkAnimation.duration = 0.5
|
||||
self.pop_add(blinkAnimation, forKey: "blinkProgress")
|
||||
}
|
||||
}
|
||||
let newProgress = min(0.99, fraction)
|
||||
@ -216,7 +254,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress, hasSeek: self.hasSeek)
|
||||
return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress, blinkProgress: self.effectiveBlinkProgress, hasSeek: self.hasSeek)
|
||||
}
|
||||
|
||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
@ -231,9 +269,23 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
if let parameters = parameters as? InstantVideoRadialStatusNodeParameters {
|
||||
context.setStrokeColor(parameters.color.cgColor)
|
||||
|
||||
context.addEllipse(in: bounds)
|
||||
context.clip()
|
||||
|
||||
if !parameters.dimProgress.isZero {
|
||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.35 * min(1.0, parameters.dimProgress)).cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
if parameters.playProgress == 1.0 {
|
||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.35 * min(1.0, parameters.dimProgress)).cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
} else {
|
||||
var locations: [CGFloat] = [0.0, 0.8, 1.0]
|
||||
let alpha: CGFloat = 0.2 + 0.15 * parameters.playProgress
|
||||
let colors: [CGColor] = [UIColor(rgb: 0x000000, alpha: alpha * min(1.0, parameters.dimProgress * parameters.playProgress)).cgColor, UIColor(rgb: 0x000000, alpha: alpha * min(1.0, parameters.dimProgress * parameters.playProgress)).cgColor, UIColor(rgb: 0x000000, alpha: alpha * min(1.0, parameters.dimProgress)).cgColor]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
let center = bounds.center
|
||||
context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: bounds.width / 2.0, options: .drawsAfterEndLocation)
|
||||
}
|
||||
}
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
@ -255,9 +307,19 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
}
|
||||
|
||||
if !parameters.dimProgress.isZero {
|
||||
context.setLineWidth(lineWidth)
|
||||
|
||||
if parameters.blinkProgress > 0.0 {
|
||||
context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.blinkProgress).cgColor)
|
||||
context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter))
|
||||
}
|
||||
|
||||
if parameters.hasSeek {
|
||||
context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.dimProgress).cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
var progress = parameters.dimProgress
|
||||
if parameters.blinkProgress < 0.0 {
|
||||
progress = parameters.dimProgress + parameters.blinkProgress
|
||||
}
|
||||
context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * progress).cgColor)
|
||||
context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter))
|
||||
}
|
||||
|
||||
|
@ -206,32 +206,28 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
|
||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, displayBackground: true)
|
||||
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo
|
||||
mediaAccessoryPanel.getController = { [weak self] in
|
||||
return self?.parentController
|
||||
}
|
||||
mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in
|
||||
self?.parentController?.presentInGlobalOverlay(c)
|
||||
}
|
||||
mediaAccessoryPanel.close = { [weak self] in
|
||||
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
|
||||
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
|
||||
}
|
||||
}
|
||||
mediaAccessoryPanel.toggleRate = {
|
||||
[weak self] in
|
||||
mediaAccessoryPanel.setRate = { [weak self] rate in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in
|
||||
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings
|
||||
|
||||
let nextRate: AudioPlaybackRate
|
||||
switch settings.voicePlaybackRate {
|
||||
case .x1:
|
||||
nextRate = .x2
|
||||
case .x2:
|
||||
nextRate = .x1
|
||||
default:
|
||||
nextRate = .x1
|
||||
}
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
||||
return settings.withUpdatedVoicePlaybackRate(nextRate)
|
||||
return settings.withUpdatedVoicePlaybackRate(rate)
|
||||
})
|
||||
return nextRate
|
||||
return rate
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { baseRate in
|
||||
guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else {
|
||||
@ -250,22 +246,31 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
})
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let slowdown = baseRate == .x1
|
||||
controller.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
slowdown: slowdown,
|
||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
let slowdown: Bool?
|
||||
if baseRate == .x1 {
|
||||
slowdown = true
|
||||
} else if baseRate == .x2 {
|
||||
slowdown = false
|
||||
} else {
|
||||
slowdown = nil
|
||||
}
|
||||
if let slowdown = slowdown {
|
||||
controller.present(
|
||||
UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .audioRate(
|
||||
slowdown: slowdown,
|
||||
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { action in
|
||||
return true
|
||||
}
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: hasTooltip,
|
||||
action: { action in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
private let hasContactSelector: Bool
|
||||
private let hasGlobalSearch: Bool
|
||||
private let pretendPresentedInModal: Bool
|
||||
private let forwardedMessagesCount: Int
|
||||
|
||||
override public var _presentedInModal: Bool {
|
||||
get {
|
||||
@ -85,6 +86,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
self.attemptSelection = params.attemptSelection
|
||||
self.createNewGroup = params.createNewGroup
|
||||
self.pretendPresentedInModal = params.pretendPresentedInModal
|
||||
self.forwardedMessagesCount = params.forwardedMessagesCount
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
@ -148,7 +150,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
|
||||
self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessagesCount: self.forwardedMessagesCount, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, presentInGlobalOverlay: { [weak self] c, a in
|
||||
self?.presentInGlobalOverlay(c, with: a)
|
||||
|
@ -66,7 +66,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
return self.readyValue.get()
|
||||
}
|
||||
|
||||
init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
||||
init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessagesCount: Int, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.present = present
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
@ -79,6 +79,16 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
|
||||
|
||||
var mockMessageIds: [MessageId]?
|
||||
if forwardedMessagesCount > 0 {
|
||||
var messageIds: [MessageId] = []
|
||||
for _ in 0 ..< forwardedMessagesCount {
|
||||
messageIds.append(MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: Int32.random(in: 0 ..< Int32.max)))
|
||||
}
|
||||
mockMessageIds = messageIds
|
||||
}
|
||||
self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(mockMessageIds) }
|
||||
|
||||
if hasChatListSelector && hasContactSelector {
|
||||
self.toolbarBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
||||
|
||||
@ -276,13 +286,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
guard let textInputNode = textInputPanelNode.textInputNode else {
|
||||
return
|
||||
}
|
||||
// let previousSupportedOrientations = strongSelf.supportedOrientations
|
||||
// if layout.size.width > layout.size.height {
|
||||
// strongSelf.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .landscape)
|
||||
// } else {
|
||||
// strongSelf.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
// }
|
||||
|
||||
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, interfaceState: strongSelf.presentationInterfaceState, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
// strongSelf.supportedOrientations = previousSupportedOrientations
|
||||
@ -292,7 +295,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}, schedule: { [weak textInputPanelNode] in
|
||||
textInputPanelNode?.sendMessage(.schedule)
|
||||
})
|
||||
// strongSelf.sendMessageActionsController = controller
|
||||
strongSelf.presentInGlobalOverlay(controller, nil)
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user