mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various fixes
This commit is contained in:
parent
6ec58eb9b4
commit
fb50102b06
9 changed files with 198 additions and 144 deletions
|
|
@ -10489,6 +10489,7 @@ Sorry for the inconvenience.";
|
|||
"Chat.SimilarChannels" = "Similar Channels";
|
||||
"Chat.SimilarChannels.Join" = "Join";
|
||||
"Chat.SimilarChannels.JoinedChannel" = "You joined channel **%@**.";
|
||||
"Chat.SimilarChannels.MoreChannels" = "More Channels";
|
||||
|
||||
"Wallpaper.ApplyForMe" = "Apply for Me";
|
||||
"Wallpaper.ApplyForBoth" = "Apply for Me and %@";
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ private final class CameraContext {
|
|||
private var invalidated = false
|
||||
|
||||
private let detectedCodesPipe = ValuePipe<[CameraCode]>()
|
||||
private let audioLevelPipe = ValuePipe<Float>()
|
||||
fileprivate let modeChangePromise = ValuePromise<Camera.ModeChange>(.none)
|
||||
|
||||
var previewView: CameraPreviewView?
|
||||
|
|
@ -281,6 +282,10 @@ private final class CameraContext {
|
|||
}
|
||||
}
|
||||
|
||||
private var micLevelPeak: Int16 = 0
|
||||
private var micLevelPeakCount = 0
|
||||
|
||||
|
||||
private var isDualCameraEnabled: Bool?
|
||||
public func setDualCameraEnabled(_ enabled: Bool, change: Bool = true) {
|
||||
guard enabled != self.isDualCameraEnabled else {
|
||||
|
|
@ -352,6 +357,48 @@ private final class CameraContext {
|
|||
self.lastSnapshotTimestamp = timestamp
|
||||
}
|
||||
}
|
||||
if self.initialConfiguration.reportAudioLevel {
|
||||
self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer)
|
||||
let numSamplesInBuffer = CMSampleBufferGetNumSamples(sampleBuffer)
|
||||
var audioBufferList = AudioBufferList()
|
||||
|
||||
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, bufferListSizeNeededOut: nil, bufferListOut: &audioBufferList, bufferListSize: MemoryLayout<AudioBufferList>.size, blockBufferAllocator: nil, blockBufferMemoryAllocator: nil, flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, blockBufferOut: &blockBuffer)
|
||||
|
||||
// for bufferCount in 0..<Int(audioBufferList.mNumberBuffers) {
|
||||
let buffer = audioBufferList.mBuffers.mData
|
||||
let size = audioBufferList.mBuffers.mDataByteSize
|
||||
if let data = buffer?.bindMemory(to: Int16.self, capacity: Int(size)) {
|
||||
processWaveformPreview(samples: data, count: numSamplesInBuffer)
|
||||
}
|
||||
// }
|
||||
|
||||
func processWaveformPreview(samples: UnsafePointer<Int16>, count: Int) {
|
||||
for i in 0..<count {
|
||||
var sample = samples[i]
|
||||
if sample < 0 {
|
||||
sample = -sample
|
||||
}
|
||||
|
||||
if self.micLevelPeak < sample {
|
||||
self.micLevelPeak = sample
|
||||
}
|
||||
self.micLevelPeakCount += 1
|
||||
|
||||
if self.micLevelPeakCount >= 1200 {
|
||||
let level = Float(self.micLevelPeak) / 4000.0
|
||||
self.audioLevelPipe.putNext(level)
|
||||
|
||||
self.micLevelPeak = 0
|
||||
self.micLevelPeakCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mainDeviceContext?.output.processCodes = { [weak self] codes in
|
||||
self?.detectedCodesPipe.putNext(codes)
|
||||
}
|
||||
|
|
@ -526,6 +573,10 @@ private final class CameraContext {
|
|||
return self.detectedCodesPipe.signal()
|
||||
}
|
||||
|
||||
var audioLevel: Signal<Float, NoError> {
|
||||
return self.audioLevelPipe.signal()
|
||||
}
|
||||
|
||||
@objc private func sessionInterruptionEnded(notification: NSNotification) {
|
||||
}
|
||||
|
||||
|
|
@ -564,8 +615,9 @@ public final class Camera {
|
|||
let metadata: Bool
|
||||
let preferredFps: Double
|
||||
let preferWide: Bool
|
||||
let reportAudioLevel: Bool
|
||||
|
||||
public init(preset: Preset, position: Position, isDualEnabled: Bool = false, audio: Bool, photo: Bool, metadata: Bool, preferredFps: Double, preferWide: Bool = false) {
|
||||
public init(preset: Preset, position: Position, isDualEnabled: Bool = false, audio: Bool, photo: Bool, metadata: Bool, preferredFps: Double, preferWide: Bool = false, reportAudioLevel: Bool = false) {
|
||||
self.preset = preset
|
||||
self.position = position
|
||||
self.isDualEnabled = isDualEnabled
|
||||
|
|
@ -574,6 +626,7 @@ public final class Camera {
|
|||
self.metadata = metadata
|
||||
self.preferredFps = preferredFps
|
||||
self.preferWide = preferWide
|
||||
self.reportAudioLevel = reportAudioLevel
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -865,6 +918,20 @@ public final class Camera {
|
|||
}
|
||||
}
|
||||
|
||||
public var audioLevel: Signal<Float, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.queue.async {
|
||||
if let context = self.contextRef?.takeUnretainedValue() {
|
||||
disposable.set(context.audioLevel.start(next: { codes in
|
||||
subscriber.putNext(codes)
|
||||
}))
|
||||
}
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public enum ModeChange: Equatable {
|
||||
case none
|
||||
case position
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ final class CameraOutput: NSObject {
|
|||
private var videoRecorder: VideoRecorder?
|
||||
|
||||
var processSampleBuffer: ((CMSampleBuffer, CVImageBuffer, AVCaptureConnection) -> Void)?
|
||||
var processAudioBuffer: ((CMSampleBuffer) -> Void)?
|
||||
var processCodes: (([CameraCode]) -> Void)?
|
||||
|
||||
init(exclusive: Bool) {
|
||||
|
|
@ -379,6 +380,8 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA
|
|||
|
||||
if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
|
||||
self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection)
|
||||
} else {
|
||||
self.processAudioBuffer?(sampleBuffer)
|
||||
}
|
||||
|
||||
if let videoRecorder = self.videoRecorder, videoRecorder.isRecording {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ public final class EntityVideoRecorder {
|
|||
photo: false,
|
||||
metadata: false,
|
||||
preferredFps: 60.0,
|
||||
preferWide: true
|
||||
preferWide: true,
|
||||
reportAudioLevel: true
|
||||
),
|
||||
previewView: self.previewView,
|
||||
secondaryPreviewView: nil
|
||||
|
|
@ -73,7 +74,7 @@ public final class EntityVideoRecorder {
|
|||
}
|
||||
}
|
||||
|
||||
self.micLevelPromise.set(.single(0.0))
|
||||
self.micLevelPromise.set(camera.audioLevel)
|
||||
|
||||
let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0
|
||||
mediaEditor.stop()
|
||||
|
|
|
|||
|
|
@ -544,11 +544,6 @@ private class MessageBackgroundNode: ASDisplayNode {
|
|||
private let itemSize = CGSize(width: 84.0, height: 90.0)
|
||||
|
||||
private final class ChannelItemComponent: Component {
|
||||
class ExternalState {
|
||||
var cachedPlaceholderImage: UIImage?
|
||||
}
|
||||
|
||||
let externalState: ExternalState
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
|
@ -561,7 +556,6 @@ private final class ChannelItemComponent: Component {
|
|||
let contextAction: ((EnginePeer, UIView, ContextGesture?) -> Void)?
|
||||
|
||||
init(
|
||||
externalState: ExternalState,
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
|
|
@ -573,7 +567,6 @@ private final class ChannelItemComponent: Component {
|
|||
openMore: @escaping () -> Void,
|
||||
contextAction: ((EnginePeer, UIView, ContextGesture?) -> Void)?
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
|
@ -677,8 +670,11 @@ private final class ChannelItemComponent: Component {
|
|||
}
|
||||
|
||||
func update(component: ChannelItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let themeUpdated = previousComponent?.theme !== component.theme
|
||||
|
||||
self.contextContainer.isGestureEnabled = true
|
||||
|
||||
|
|
@ -776,69 +772,43 @@ private final class ChannelItemComponent: Component {
|
|||
}
|
||||
self.circlesView.isHidden = false
|
||||
|
||||
if self.circlesView.image == nil {
|
||||
if let current = component.externalState.cachedPlaceholderImage {
|
||||
self.circlesView.image = current
|
||||
} else {
|
||||
let image = generateImage(CGSize(width: 50.0, height: avatarSize.height), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
|
||||
let randomColors: [(UInt32, UInt32)] = [
|
||||
(0x4493de, 0x52d5d9),
|
||||
(0xfcc418, 0xf6774a),
|
||||
(0xffc9a2, 0xfbedb2),
|
||||
(0x133e88, 0x131925),
|
||||
(0x63c7f0, 0xf6c506),
|
||||
(0x88a5cb, 0x162639),
|
||||
(0xd669ed, 0xe0a2f3),
|
||||
(0x54cb68, 0xa0de7e)
|
||||
]
|
||||
|
||||
context.saveGState()
|
||||
|
||||
let rect1 = CGRect(origin: CGPoint(x: size.width - avatarSize.width, y: 0.0), size: avatarSize)
|
||||
context.addEllipse(in: rect1)
|
||||
context.clip()
|
||||
|
||||
var firstColors: NSArray = []
|
||||
if let random = randomColors.randomElement() {
|
||||
firstColors = [UIColor(rgb: random.0).cgColor, UIColor(rgb: random.1).cgColor]
|
||||
}
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let firstGradient = CGGradient(colorsSpace: colorSpace, colors: firstColors as CFArray, locations: &locations)!
|
||||
context.drawLinearGradient(firstGradient, start: CGPoint(x: rect1.minX, y: rect1.minY), end: CGPoint(x: rect1.maxX, y: rect1.maxY), options: CGGradientDrawingOptions())
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - avatarSize.width - 12.0, y: -2.0), size: CGSize(width: avatarSize.width + 4.0, height: avatarSize.height + 4.0)))
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
context.saveGState()
|
||||
|
||||
let rect2 = CGRect(origin: CGPoint(x: size.width - avatarSize.width - 10.0, y: 0.0), size: avatarSize)
|
||||
context.addEllipse(in: rect2)
|
||||
context.clip()
|
||||
|
||||
var secondColors: NSArray = []
|
||||
if let random = randomColors.randomElement() {
|
||||
secondColors = [UIColor(rgb: random.0).cgColor, UIColor(rgb: random.1).cgColor]
|
||||
}
|
||||
|
||||
let secondGradient = CGGradient(colorsSpace: colorSpace, colors: secondColors as CFArray, locations: &locations)!
|
||||
context.drawLinearGradient(secondGradient, start: CGPoint(x: rect2.minX, y: rect2.minY), end: CGPoint(x: rect2.minX, y: rect2.maxY), options: CGGradientDrawingOptions())
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - avatarSize.width - 22.0, y: -2.0), size: CGSize(width: avatarSize.width + 4.0, height: avatarSize.height + 4.0)))
|
||||
})
|
||||
component.externalState.cachedPlaceholderImage = image
|
||||
self.circlesView.image = image
|
||||
}
|
||||
if self.circlesView.image == nil || themeUpdated {
|
||||
let image = generateImage(CGSize(width: 50.0, height: avatarSize.height), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
|
||||
let color = component.theme.chat.message.incoming.secondaryTextColor.withMultipliedAlpha(0.35)
|
||||
|
||||
context.saveGState()
|
||||
|
||||
let rect1 = CGRect(origin: CGPoint(x: size.width - avatarSize.width, y: 0.0), size: avatarSize)
|
||||
context.addEllipse(in: rect1)
|
||||
context.clip()
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(rect1)
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - avatarSize.width - 12.0, y: -2.0), size: CGSize(width: avatarSize.width + 4.0, height: avatarSize.height + 4.0)))
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
context.saveGState()
|
||||
|
||||
let rect2 = CGRect(origin: CGPoint(x: size.width - avatarSize.width - 10.0, y: 0.0), size: avatarSize)
|
||||
context.addEllipse(in: rect2)
|
||||
context.clip()
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(rect2)
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - avatarSize.width - 22.0, y: -2.0), size: CGSize(width: avatarSize.width + 4.0, height: avatarSize.height + 4.0)))
|
||||
})
|
||||
self.circlesView.image = image
|
||||
}
|
||||
self.circlesView.frame = CGRect(origin: CGPoint(x: avatarFrame.midX, y: 0.0), size: CGSize(width: 50.0, height: 60.0))
|
||||
} else {
|
||||
|
|
@ -991,7 +961,6 @@ final class ChannelListPanelComponent: Component {
|
|||
|
||||
private let measureItem = ComponentView<Empty>()
|
||||
private var visibleItems: [EnginePeer.Id: ComponentView<Empty>] = [:]
|
||||
private var externalState = ChannelItemComponent.ExternalState()
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
|
|
@ -1072,7 +1041,7 @@ final class ChannelListPanelComponent: Component {
|
|||
if !component.context.isPremium {
|
||||
isLocked = true
|
||||
}
|
||||
title = isLocked ? "Unlock More Channels" : "View More Channels"
|
||||
title = component.strings.Chat_SimilarChannels_MoreChannels
|
||||
subtitle = "+\(component.peers.count - channelsLimit)"
|
||||
isLast = true
|
||||
} else {
|
||||
|
|
@ -1092,7 +1061,6 @@ final class ChannelListPanelComponent: Component {
|
|||
let _ = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(ChannelItemComponent(
|
||||
externalState: self.externalState,
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import MediaEditor
|
||||
import DrawingUI
|
||||
import ChatPresentationInterfaceState
|
||||
import PresentationDataUtils
|
||||
import TelegramPresentationData
|
||||
import DeviceAccess
|
||||
import AccountContext
|
||||
|
||||
extension MediaEditorScreen {
|
||||
final class Recording {
|
||||
|
|
@ -13,10 +16,56 @@ extension MediaEditorScreen {
|
|||
|
||||
private var recorder: EntityVideoRecorder?
|
||||
|
||||
private let idleTimerExtensionDisposable = MetaDisposable()
|
||||
|
||||
private var authorizationStatusDisposables = DisposableSet()
|
||||
private var cameraAuthorizationStatus: AccessType = .notDetermined
|
||||
private var microphoneAuthorizationStatus: AccessType = .notDetermined
|
||||
|
||||
fileprivate var cameraIsActive = true {
|
||||
didSet {
|
||||
guard let context = self.controller?.context else {
|
||||
return
|
||||
}
|
||||
if self.cameraIsActive {
|
||||
self.idleTimerExtensionDisposable.set(context.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||
} else {
|
||||
self.idleTimerExtensionDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isLocked = false
|
||||
|
||||
init(controller: MediaEditorScreen) {
|
||||
self.controller = controller
|
||||
|
||||
self.authorizationStatusDisposables.add((DeviceAccess.authorizationStatus(subject: .camera(.video))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self {
|
||||
self.cameraAuthorizationStatus = status
|
||||
}
|
||||
}))
|
||||
|
||||
self.authorizationStatusDisposables.add((DeviceAccess.authorizationStatus(subject: .microphone(.video))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self {
|
||||
self.microphoneAuthorizationStatus = status
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.idleTimerExtensionDisposable.dispose()
|
||||
self.authorizationStatusDisposables.dispose()
|
||||
}
|
||||
|
||||
func requestDeviceAccess() {
|
||||
DeviceAccess.authorizeAccess(to: .camera(.video), { granted in
|
||||
if granted {
|
||||
DeviceAccess.authorizeAccess(to: .microphone(.video))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setMediaRecordingActive(_ isActive: Bool, finished: Bool, sourceView: UIView?) {
|
||||
|
|
@ -29,8 +78,8 @@ extension MediaEditorScreen {
|
|||
}
|
||||
|
||||
if isActive {
|
||||
if controller.cameraAuthorizationStatus != .allowed || controller.microphoneAuthorizationStatus != .allowed {
|
||||
controller.requestDeviceAccess()
|
||||
if self.cameraAuthorizationStatus != .allowed || self.microphoneAuthorizationStatus != .allowed {
|
||||
self.requestDeviceAccess()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +102,8 @@ extension MediaEditorScreen {
|
|||
}
|
||||
self.recorder = recorder
|
||||
controller.node.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.2))
|
||||
|
||||
self.cameraIsActive = true
|
||||
} else {
|
||||
if let recorder = self.recorder {
|
||||
recorder.stopRecording(save: finished, completion: { [weak self] in
|
||||
|
|
@ -65,6 +116,8 @@ extension MediaEditorScreen {
|
|||
})
|
||||
|
||||
controller.node.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.2))
|
||||
|
||||
self.cameraIsActive = false
|
||||
} else {
|
||||
guard self.tooltipController == nil, let sourceView else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4043,11 +4043,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
private var audioSessionDisposable: Disposable?
|
||||
private let postingAvailabilityPromise = Promise<StoriesUploadAvailability>()
|
||||
private var postingAvailabilityDisposable: Disposable?
|
||||
|
||||
private var authorizationStatusDisposables = DisposableSet()
|
||||
private(set) var cameraAuthorizationStatus: AccessType = .notDetermined
|
||||
private(set) var microphoneAuthorizationStatus: AccessType = .notDetermined
|
||||
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
subject: Signal<Subject?, NoError>,
|
||||
|
|
@ -4122,20 +4118,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
if let _ = forwardSource {
|
||||
self.postingAvailabilityPromise.set(self.context.engine.messages.checkStoriesUploadAvailability(target: .myStories))
|
||||
}
|
||||
|
||||
self.authorizationStatusDisposables.add((DeviceAccess.authorizationStatus(subject: .camera(.video))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self {
|
||||
self.cameraAuthorizationStatus = status
|
||||
}
|
||||
}))
|
||||
|
||||
self.authorizationStatusDisposables.add((DeviceAccess.authorizationStatus(subject: .microphone(.video))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self {
|
||||
self.microphoneAuthorizationStatus = status
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
|
@ -4146,7 +4128,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
self.exportDisposable.dispose()
|
||||
self.audioSessionDisposable?.dispose()
|
||||
self.postingAvailabilityDisposable?.dispose()
|
||||
self.authorizationStatusDisposables.dispose()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
|
|
@ -4225,14 +4206,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
fileprivate var isEmbeddedEditor: Bool {
|
||||
return self.isEditingStory || self.forwardSource != nil
|
||||
}
|
||||
|
||||
func requestDeviceAccess() {
|
||||
DeviceAccess.authorizeAccess(to: .camera(.video), { granted in
|
||||
if granted {
|
||||
DeviceAccess.authorizeAccess(to: .microphone(.video))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) {
|
||||
self.node.mediaEditor?.maybePauseVideo()
|
||||
|
|
|
|||
|
|
@ -117,10 +117,11 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode
|
|||
return self.ready.get()
|
||||
}
|
||||
|
||||
private let statusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
return .single(nil)
|
||||
self.statusPromise.get()
|
||||
}
|
||||
|
||||
|
||||
var tabBarOffsetUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
var tabBarOffset: CGFloat {
|
||||
return 0.0
|
||||
|
|
@ -159,6 +160,16 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode
|
|||
strongSelf.currentState = (recommendedChannels, isPremium)
|
||||
strongSelf.updateState(recommendedChannels: recommendedChannels, isPremium: isPremium, presentationData: presentationData)
|
||||
})
|
||||
|
||||
self.statusPromise.set(context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId)
|
||||
)
|
||||
|> map { count -> PeerInfoStatusData? in
|
||||
if let count {
|
||||
return PeerInfoStatusData(text: presentationData.strings.Conversation_StatusSubscribers(Int32(count)), isActivity: true, key: .recommended)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
var subtitleBackgroundButton: HighlightTrackingButtonNode?
|
||||
var subtitleArrowNode: ASImageNode?
|
||||
let panelSubtitleNode: MultiScaleTextNode
|
||||
let nextPanelSubtitleNode: MultiScaleTextNode
|
||||
let usernameNodeContainer: ASDisplayNode
|
||||
let usernameNodeRawContainer: ASDisplayNode
|
||||
let usernameNode: MultiScaleTextNode
|
||||
|
|
@ -195,9 +194,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.panelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||
self.panelSubtitleNode.displaysAsynchronously = false
|
||||
|
||||
self.nextPanelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||
self.nextPanelSubtitleNode.displaysAsynchronously = false
|
||||
|
||||
self.usernameNodeContainer = ASDisplayNode()
|
||||
self.usernameNodeRawContainer = ASDisplayNode()
|
||||
self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||
|
|
@ -258,7 +254,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.titleNodeContainer.addSubnode(self.titleNode)
|
||||
self.subtitleNodeContainer.addSubnode(self.subtitleNode)
|
||||
self.subtitleNodeContainer.addSubnode(self.panelSubtitleNode)
|
||||
// self.subtitleNodeContainer.addSubnode(self.nextPanelSubtitleNode)
|
||||
self.usernameNodeContainer.addSubnode(self.usernameNode)
|
||||
|
||||
self.regularContentNode.addSubnode(self.avatarClippingNode)
|
||||
|
|
@ -778,7 +773,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.titleNode.updateTintColor(color: navigationContentsPrimaryColor, transition: navigationTransition)
|
||||
self.subtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition)
|
||||
self.panelSubtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition)
|
||||
self.nextPanelSubtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition)
|
||||
if let navigationBar = self.controller?.navigationBar {
|
||||
if let mainContentNode = navigationBar.backButtonNode.mainContentNode {
|
||||
navigationTransition.updateTintColor(layer: mainContentNode.layer, color: navigationContentsAccentColor)
|
||||
|
|
@ -839,7 +833,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let subtitleAttributes: MultiScaleTextState.Attributes
|
||||
var subtitleIsButton: Bool = false
|
||||
var panelSubtitleString: (text: String, attributes: MultiScaleTextState.Attributes)?
|
||||
var nextPanelSubtitleString: (text: String, attributes: MultiScaleTextState.Attributes)?
|
||||
let usernameString: (text: String, attributes: MultiScaleTextState.Attributes)
|
||||
if let peer = peer {
|
||||
isPremium = peer.isPremium
|
||||
|
|
@ -899,7 +892,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
|
||||
subtitleIsButton = true
|
||||
|
||||
let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData
|
||||
let (maybePanelStatusData, _, _) = panelStatusData
|
||||
if let panelStatusData = maybePanelStatusData {
|
||||
let subtitleColor: UIColor
|
||||
if panelStatusData.isActivity {
|
||||
|
|
@ -909,9 +902,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
|
||||
}
|
||||
if let nextPanelStatusData = maybeNextPanelStatusData {
|
||||
nextPanelSubtitleString = (nextPanelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: .white))
|
||||
}
|
||||
} else if let statusData = statusData {
|
||||
let subtitleColor: UIColor
|
||||
if statusData.isActivity {
|
||||
|
|
@ -926,7 +916,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
|
||||
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
|
||||
|
||||
let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData
|
||||
let (maybePanelStatusData, _, _) = panelStatusData
|
||||
if let panelStatusData = maybePanelStatusData {
|
||||
let subtitleColor: UIColor
|
||||
if panelStatusData.isActivity {
|
||||
|
|
@ -936,9 +926,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
|
||||
}
|
||||
if let nextPanelStatusData = maybeNextPanelStatusData {
|
||||
nextPanelSubtitleString = (nextPanelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: .white))
|
||||
}
|
||||
} else {
|
||||
subtitleStringText = " "
|
||||
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
|
||||
|
|
@ -1071,14 +1058,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
], mainState: TitleNodeStateRegular)
|
||||
self.panelSubtitleNode.accessibilityLabel = panelSubtitleString?.text ?? subtitleStringText
|
||||
|
||||
let nextPanelSubtitleNodeLayout = self.nextPanelSubtitleNode.updateLayout(text: nextPanelSubtitleString?.text ?? subtitleStringText, states: [
|
||||
TitleNodeStateRegular: MultiScaleTextState(attributes: nextPanelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize),
|
||||
TitleNodeStateExpanded: MultiScaleTextState(attributes: nextPanelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize)
|
||||
], mainState: TitleNodeStateRegular)
|
||||
if let _ = nextPanelSubtitleString {
|
||||
self.nextPanelSubtitleNode.isHidden = false
|
||||
}
|
||||
|
||||
let usernameNodeLayout = self.usernameNode.updateLayout(text: usernameString.text, states: [
|
||||
TitleNodeStateRegular: MultiScaleTextState(attributes: usernameString.attributes, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
|
||||
TitleNodeStateExpanded: MultiScaleTextState(attributes: usernameString.attributes, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
|
||||
|
|
@ -1096,7 +1075,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size
|
||||
let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size
|
||||
let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size
|
||||
let _ = nextPanelSubtitleNodeLayout[TitleNodeStateRegular]!.size
|
||||
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
|
||||
|
||||
var titleHorizontalOffset: CGFloat = 0.0
|
||||
|
|
@ -1203,11 +1181,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
if (panelSubtitleString?.text ?? subtitleStringText) != subtitleStringText {
|
||||
subtitleAlpha = 1.0 - effectiveAreaExpansionFraction
|
||||
panelSubtitleAlpha = effectiveAreaExpansionFraction
|
||||
|
||||
subtitleOffset = -effectiveAreaExpansionFraction * 5.0
|
||||
panelSubtitleOffset = (1.0 - effectiveAreaExpansionFraction) * 5.0
|
||||
} else {
|
||||
subtitleAlpha = 1.0
|
||||
panelSubtitleAlpha = 0.0
|
||||
if effectiveAreaExpansionFraction == 1.0 {
|
||||
subtitleAlpha = 0.0
|
||||
panelSubtitleAlpha = 1.0
|
||||
} else {
|
||||
subtitleAlpha = 1.0
|
||||
panelSubtitleAlpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
self.subtitleNode.update(stateFractions: [
|
||||
|
|
@ -1220,11 +1204,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||
], alpha: panelSubtitleAlpha, transition: transition)
|
||||
|
||||
self.nextPanelSubtitleNode.update(stateFractions: [
|
||||
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
||||
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||
], alpha: panelSubtitleAlpha, transition: transition)
|
||||
|
||||
self.usernameNode.update(stateFractions: [
|
||||
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
||||
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||
|
|
@ -1501,8 +1480,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
|
||||
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()))
|
||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
|
||||
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize()))
|
||||
transition.updateFrame(node: self.nextPanelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize()))
|
||||
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
|
||||
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
|
||||
transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale)
|
||||
|
|
@ -1517,7 +1495,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
} else {
|
||||
titleScale = (1.0 - titleCollapseFraction) * 1.0 + titleCollapseFraction * titleMinScale
|
||||
subtitleScale = (1.0 - titleCollapseFraction) * 1.0 + titleCollapseFraction * subtitleMinScale
|
||||
subtitleOffset = titleCollapseFraction * -2.0
|
||||
subtitleOffset = titleCollapseFraction * -1.0
|
||||
}
|
||||
|
||||
let rawTitleFrame = titleFrame.offsetBy(dx: self.isAvatarExpanded ? 0.0 : titleHorizontalOffset * titleScale, dy: 0.0)
|
||||
|
|
@ -1544,8 +1522,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
|
||||
}
|
||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
|
||||
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize()))
|
||||
transition.updateFrame(node: self.nextPanelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize()))
|
||||
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
|
||||
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
|
||||
transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue