Various improvements

This commit is contained in:
Isaac 2024-08-20 22:09:13 +08:00
parent bdfe639f3e
commit 67ed88e951
17 changed files with 815 additions and 34 deletions

View File

@ -12811,9 +12811,15 @@ Sorry for the inconvenience.";
"Chat.ToastStarsSent.Title_1" = "Star sent!"; "Chat.ToastStarsSent.Title_1" = "Star sent!";
"Chat.ToastStarsSent.Title_any" = "Stars sent!"; "Chat.ToastStarsSent.Title_any" = "Stars sent!";
"Chat.ToastStarsSent.AnonymousTitle_1" = "Star sent anonymously!";
"Chat.ToastStarsSent.AnonymousTitle_any" = "Stars sent anonymously!";
"Chat.ToastStarsSent.Text" = "You have reacted with %1$@ %2$@."; "Chat.ToastStarsSent.Text" = "You have reacted with %1$@ %2$@.";
"Chat.ToastStarsSent.TextStarAmount_1" = "star"; "Chat.ToastStarsSent.TextStarAmount_1" = "star";
"Chat.ToastStarsSent.TextStarAmount_any" = "stars"; "Chat.ToastStarsSent.TextStarAmount_any" = "stars";
"Stars.Purchase.StarsReactionsNeededInfo" = "Buy Stars to send paid reactions **%@** and other channels."; "Stars.Purchase.StarsReactionsNeededInfo" = "Buy Stars to send paid reactions **%@** and other channels.";
"Chat.ToastStarsReactionsDisabled" = "Star Reactions were disabled in %@";" "Chat.ToastStarsReactionsDisabled" = "Star Reactions were disabled in %@";
"ChatContextMenu.SingleReactionEmojiSet" = "This reaction is from #[%@]() emoji pack.";

View File

@ -94,6 +94,7 @@ public final class ReactionIconView: PortalSourceView {
private var animateIdle: Bool? private var animateIdle: Bool?
private var reaction: MessageReaction.Reaction? private var reaction: MessageReaction.Reaction?
private var isPaused: Bool = false
private var isAnimationHidden: Bool = false private var isAnimationHidden: Bool = false
private var disposable: Disposable? private var disposable: Disposable?
@ -198,6 +199,29 @@ public final class ReactionIconView: PortalSourceView {
} }
} }
func updateIsPaused(isPaused: Bool) {
guard let context = self.context, let animateIdle = self.animateIdle, let animationLayer = self.animationLayer else {
return
}
self.isPaused = isPaused
let isVisibleForAnimations = !self.isPaused && animateIdle && context.sharedContext.energyUsageSettings.loopEmoji
if isVisibleForAnimations != animationLayer.isVisibleForAnimations {
if isPaused {
animationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak animationLayer] _ in
animationLayer?.removeFromSuperlayer()
})
self.animationLayer = nil
self.reloadFile()
if let animationLayer = self.animationLayer {
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
}
} else {
animationLayer.isVisibleForAnimations = !self.isPaused && animateIdle && context.sharedContext.energyUsageSettings.loopEmoji
}
}
}
private func reloadFile() { private func reloadFile() {
guard let context = self.context, let file = self.file, let animationCache = self.animationCache, let animationRenderer = self.animationRenderer, let placeholderColor = self.placeholderColor, let size = self.size, let animateIdle = self.animateIdle, let reaction = self.reaction else { guard let context = self.context, let file = self.file, let animationCache = self.animationCache, let animationRenderer = self.animationRenderer, let placeholderColor = self.placeholderColor, let size = self.size, let animateIdle = self.animateIdle, let reaction = self.reaction else {
return return
@ -217,7 +241,7 @@ public final class ReactionIconView: PortalSourceView {
} }
let animationLayer = InlineStickerItemLayer( let animationLayer = InlineStickerItemLayer(
context: context, context: .account(context),
userLocation: .other, userLocation: .other,
attemptSynchronousLoad: false, attemptSynchronousLoad: false,
emoji: ChatTextInputTextCustomEmojiAttribute( emoji: ChatTextInputTextCustomEmojiAttribute(
@ -228,6 +252,7 @@ public final class ReactionIconView: PortalSourceView {
file: file, file: file,
cache: animationCache, cache: animationCache,
renderer: animationRenderer, renderer: animationRenderer,
unique: true,
placeholderColor: placeholderColor, placeholderColor: placeholderColor,
pointSize: CGSize(width: iconSize.width * 2.0, height: iconSize.height * 2.0) pointSize: CGSize(width: iconSize.width * 2.0, height: iconSize.height * 2.0)
) )
@ -248,7 +273,7 @@ public final class ReactionIconView: PortalSourceView {
animationLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) animationLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
animationLayer.isVisibleForAnimations = animateIdle && context.sharedContext.energyUsageSettings.loopEmoji animationLayer.isVisibleForAnimations = !self.isPaused && animateIdle && context.sharedContext.energyUsageSettings.loopEmoji
self.updateTintColor() self.updateTintColor()
} }
@ -883,10 +908,14 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
self.beginDelay = 0.0 self.beginDelay = 0.0
self.containerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in self.containerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true) self.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true)
if let iconView = self.iconView {
iconView.updateIsPaused(isPaused: isExtracted)
}
} }
if self.activateAfterCompletion { if self.activateAfterCompletion {

View File

@ -102,6 +102,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case playlistPlayback(Bool) case playlistPlayback(Bool)
case enableQuickReactionSwitch(Bool) case enableQuickReactionSwitch(Bool)
case disableReloginTokens(Bool) case disableReloginTokens(Bool)
case callV2(Bool)
case liveStreamV2(Bool) case liveStreamV2(Bool)
case preferredVideoCodec(Int, String, String?, Bool) case preferredVideoCodec(Int, String, String?, Bool)
case disableVideoAspectScaling(Bool) case disableVideoAspectScaling(Bool)
@ -127,7 +128,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2: case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .callV2, .liveStreamV2:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates: case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue return DebugControllerSection.translation.rawValue
@ -240,10 +241,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 49 return 49
case .enableQuickReactionSwitch: case .enableQuickReactionSwitch:
return 50 return 50
case .liveStreamV2: case .callV2:
return 51 return 51
case .liveStreamV2:
return 52
case let .preferredVideoCodec(index, _, _, _): case let .preferredVideoCodec(index, _, _, _):
return 52 + index return 53 + index
case .disableVideoAspectScaling: case .disableVideoAspectScaling:
return 100 return 100
case .enableNetworkFramework: case .enableNetworkFramework:
@ -1312,6 +1315,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .callV2(value):
return ItemListSwitchItem(presentationData: presentationData, title: "[WIP] Video Chat V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.callV2 = value
return PreferencesEntry(settings)
})
}).start()
})
case let .liveStreamV2(value): case let .liveStreamV2(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -1476,10 +1489,11 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
} }
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction)) entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
entries.append(.callV2(experimentalSettings.callV2))
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2)) entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
} }
let codecs: [(String, String?)] = [ /*let codecs: [(String, String?)] = [
("No Preference", nil), ("No Preference", nil),
("H265", "H265"), ("H265", "H265"),
("H264", "H264"), ("H264", "H264"),
@ -1489,7 +1503,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
for i in 0 ..< codecs.count { for i in 0 ..< codecs.count {
entries.append(.preferredVideoCodec(i, codecs[i].0, codecs[i].1, experimentalSettings.preferredVideoCodec == codecs[i].1)) entries.append(.preferredVideoCodec(i, codecs[i].0, codecs[i].1, experimentalSettings.preferredVideoCodec == codecs[i].1))
} }*/
if isMainApp { if isMainApp {
entries.append(.disableVideoAspectScaling(experimentalSettings.disableVideoAspectScaling)) entries.append(.disableVideoAspectScaling(experimentalSettings.disableVideoAspectScaling))

View File

@ -500,7 +500,7 @@ public func getSharedDevideGraphicsContextSettings() -> DeviceGraphicsContextSet
} else { } else {
self.colorSpace = context.colorSpace! self.colorSpace = context.colorSpace!
} }
assert(self.rowAlignment == 32) assert(self.rowAlignment == 32 || self.rowAlignment == 64)
assert(self.bitsPerPixel == 32) assert(self.bitsPerPixel == 32)
assert(self.bitsPerComponent == 8) assert(self.bitsPerComponent == 8)
} }
@ -570,7 +570,8 @@ public struct DeviceGraphicsContextSettings {
public func bytesPerRow(forWidth width: Int) -> Int { public func bytesPerRow(forWidth width: Int) -> Int {
let baseValue = self.bitsPerPixel * width / 8 let baseValue = self.bitsPerPixel * width / 8
return (baseValue + 31) & ~0x1F let alignmentMask = self.rowAlignment - 1
return (baseValue + alignmentMask) & ~alignmentMask
} }
} }

View File

@ -111,6 +111,7 @@ swift_library(
"//submodules/ImageBlur", "//submodules/ImageBlur",
"//submodules/MetalEngine", "//submodules/MetalEngine",
"//submodules/TelegramUI/Components/Calls/VoiceChatActionButton", "//submodules/TelegramUI/Components/Calls/VoiceChatActionButton",
"//submodules/TelegramUI/Components/PlainButtonComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -0,0 +1,465 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import Postbox
import TelegramCore
import AccountContext
import PlainButtonComponent
import SwiftSignalKit
import MultilineTextComponent
import MetalEngine
import CallScreen
private final class ParticipantVideoComponent: Component {
let call: PresentationGroupCall
let participant: GroupCallParticipantsContext.Participant
init(
call: PresentationGroupCall,
participant: GroupCallParticipantsContext.Participant
) {
self.call = call
self.participant = participant
}
static func ==(lhs: ParticipantVideoComponent, rhs: ParticipantVideoComponent) -> Bool {
if lhs.participant != rhs.participant {
return false
}
return true
}
private struct VideoSpec: Equatable {
var resolution: CGSize
var rotationAngle: Float
init(resolution: CGSize, rotationAngle: Float) {
self.resolution = resolution
self.rotationAngle = rotationAngle
}
}
final class View: UIView {
private var component: ParticipantVideoComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private let title = ComponentView<Empty>()
private var videoSource: AdaptedCallVideoSource?
private var videoDisposable: Disposable?
private var videoLayer: PrivateCallVideoLayer?
private var videoSpec: VideoSpec?
override init(frame: CGRect) {
super.init(frame: frame)
self.clipsToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.videoDisposable?.dispose()
}
func update(component: ParticipantVideoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component
self.state = state
let nameColor = component.participant.peer.nameColor ?? .blue
let nameColors = component.call.accountContext.peerNameColors.get(nameColor, dark: true)
self.backgroundColor = nameColors.main
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.participant.peer.debugDisplayTitle, font: Font.regular(14.0), textColor: .white))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - 8.0 * 2.0, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: 8.0, y: availableSize.height - 8.0 - titleSize.height), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.layer.anchorPoint = CGPoint()
self.addSubview(titleView)
}
transition.setPosition(view: titleView, position: titleFrame.origin)
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
}
if let videoDescription = component.participant.videoDescription {
let _ = videoDescription
let videoLayer: PrivateCallVideoLayer
if let current = self.videoLayer {
videoLayer = current
} else {
videoLayer = PrivateCallVideoLayer()
self.videoLayer = videoLayer
self.layer.insertSublayer(videoLayer, at: 0)
if let input = (component.call as! PresentationGroupCallImpl).video(endpointId: videoDescription.endpointId) {
let videoSource = AdaptedCallVideoSource(videoStreamSignal: input)
self.videoSource = videoSource
self.videoDisposable?.dispose()
self.videoDisposable = videoSource.addOnUpdated { [weak self] in
guard let self, let videoSource = self.videoSource, let videoLayer = self.videoLayer else {
return
}
let videoOutput = videoSource.currentOutput
videoLayer.video = videoOutput
if let videoOutput {
let videoSpec = VideoSpec(resolution: videoOutput.resolution, rotationAngle: videoOutput.rotationAngle)
if self.videoSpec != videoSpec {
self.videoSpec = videoSpec
if !self.isUpdating {
self.state?.updated(transition: .immediate, isLocal: true)
}
}
} else {
if self.videoSpec != nil {
self.videoSpec = nil
if !self.isUpdating {
self.state?.updated(transition: .immediate, isLocal: true)
}
}
}
/*var notifyOrientationUpdated = false
var notifyIsMirroredUpdated = false
if !self.didReportFirstFrame {
notifyOrientationUpdated = true
notifyIsMirroredUpdated = true
}
if let currentOutput = videoOutput {
let currentAspect: CGFloat
if currentOutput.resolution.height > 0.0 {
currentAspect = currentOutput.resolution.width / currentOutput.resolution.height
} else {
currentAspect = 1.0
}
if self.currentAspect != currentAspect {
self.currentAspect = currentAspect
notifyOrientationUpdated = true
}
let currentOrientation: PresentationCallVideoView.Orientation
if currentOutput.followsDeviceOrientation {
currentOrientation = .rotation0
} else {
if abs(currentOutput.rotationAngle - 0.0) < .ulpOfOne {
currentOrientation = .rotation0
} else if abs(currentOutput.rotationAngle - Float.pi * 0.5) < .ulpOfOne {
currentOrientation = .rotation90
} else if abs(currentOutput.rotationAngle - Float.pi) < .ulpOfOne {
currentOrientation = .rotation180
} else if abs(currentOutput.rotationAngle - Float.pi * 3.0 / 2.0) < .ulpOfOne {
currentOrientation = .rotation270
} else {
currentOrientation = .rotation0
}
}
if self.currentOrientation != currentOrientation {
self.currentOrientation = currentOrientation
notifyOrientationUpdated = true
}
let currentIsMirrored = !currentOutput.mirrorDirection.isEmpty
if self.currentIsMirrored != currentIsMirrored {
self.currentIsMirrored = currentIsMirrored
notifyIsMirroredUpdated = true
}
}
if !self.didReportFirstFrame {
self.didReportFirstFrame = true
self.onFirstFrameReceived?(Float(self.currentAspect))
}
if notifyOrientationUpdated {
self.onOrientationUpdated?(self.currentOrientation, self.currentAspect)
}
if notifyIsMirroredUpdated {
self.onIsMirroredUpdated?(self.currentIsMirrored)
}*/
}
}
}
transition.setFrame(layer: videoLayer, frame: CGRect(origin: CGPoint(), size: availableSize))
if let videoSpec = self.videoSpec {
let rotatedResolution = videoSpec.resolution
let videoSize = rotatedResolution.aspectFilled(availableSize)
let videoFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - videoSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - videoSize.height) * 0.5)), size: videoSize)
let videoResolution = rotatedResolution.aspectFittedOrSmaller(CGSize(width: availableSize.width, height: availableSize.height)).aspectFittedOrSmaller(CGSize(width: videoSize.width * 3.0, height: videoSize.height * 3.0))
let rotatedVideoResolution = videoResolution
transition.setPosition(layer: videoLayer, position: videoFrame.center)
transition.setBounds(layer: videoLayer, bounds: CGRect(origin: CGPoint(), size: videoFrame.size))
videoLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(rotatedVideoResolution.width), height: Int(rotatedVideoResolution.height)), edgeInset: 2)
}
} else {
if let videoLayer = self.videoLayer {
self.videoLayer = nil
videoLayer.removeFromSuperlayer()
}
self.videoDisposable?.dispose()
self.videoDisposable = nil
self.videoSource = nil
self.videoSpec = nil
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class VideoChatParticipantsComponent: Component {
let call: PresentationGroupCall
let members: PresentationGroupCallMembers?
init(
call: PresentationGroupCall,
members: PresentationGroupCallMembers?
) {
self.call = call
self.members = members
}
static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool {
if lhs.members != rhs.members {
return false
}
return true
}
private final class ScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
private final class ItemLayout {
let containerSize: CGSize
let itemCount: Int
let itemSize: CGSize
let itemSpacing: CGFloat
let lastItemSize: CGFloat
let itemsPerRow: Int
init(containerSize: CGSize, itemCount: Int) {
self.containerSize = containerSize
self.itemCount = itemCount
let width: CGFloat = containerSize.width
self.itemSpacing = 1.0
let itemsPerRow: CGFloat = CGFloat(3)
self.itemsPerRow = Int(itemsPerRow)
let itemSize = floorToScreenPixels((width - (self.itemSpacing * CGFloat(self.itemsPerRow - 1))) / itemsPerRow)
self.itemSize = CGSize(width: itemSize, height: itemSize)
self.lastItemSize = width - (self.itemSize.width + self.itemSpacing) * CGFloat(self.itemsPerRow - 1)
}
func frame(at index: Int) -> CGRect {
let row = index / self.itemsPerRow
let column = index % self.itemsPerRow
let frame = CGRect(origin: CGPoint(x: CGFloat(column) * (self.itemSize.width + self.itemSpacing), y: CGFloat(row) * (self.itemSize.height + self.itemSpacing)), size: CGSize(width: column == (self.itemsPerRow - 1) ? self.lastItemSize : itemSize.width, height: itemSize.height))
return frame
}
func contentHeight() -> CGFloat {
return self.frame(at: self.itemCount - 1).maxY
}
func visibleItemRange(for rect: CGRect, count: Int) -> (minIndex: Int, maxIndex: Int) {
let offsetRect = rect.offsetBy(dx: 0.0, dy: 0.0)
var minVisibleRow = Int(floor((offsetRect.minY - self.itemSpacing) / (self.itemSize.height + self.itemSpacing)))
minVisibleRow = max(0, minVisibleRow)
let maxVisibleRow = Int(ceil((offsetRect.maxY - self.itemSpacing) / (self.itemSize.height + itemSpacing)))
let minVisibleIndex = minVisibleRow * self.itemsPerRow
let maxVisibleIndex = min(count - 1, (maxVisibleRow + 1) * self.itemsPerRow - 1)
return (minVisibleIndex, maxVisibleIndex)
}
}
final class View: UIView, UIScrollViewDelegate {
private let scrollView: ScrollView
private var component: VideoChatParticipantsComponent?
private var isUpdating: Bool = false
private var ignoreScrolling: Bool = false
private var itemViews: [EnginePeer.Id: ComponentView<Empty>] = [:]
private var itemLayout: ItemLayout?
override init(frame: CGRect) {
self.scrollView = ScrollView()
super.init(frame: frame)
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false
self.scrollView.contentInsetAdjustmentBehavior = .never
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
}
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.alwaysBounceVertical = true
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self
self.scrollView.clipsToBounds = true
self.addSubview(self.scrollView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
}
private func updateScrolling(transition: ComponentTransition) {
guard let component = self.component, let itemLayout = self.itemLayout else {
return
}
var validItemIds: [EnginePeer.Id] = []
if let members = component.members {
let visibleItemRange = itemLayout.visibleItemRange(for: self.scrollView.bounds, count: itemLayout.itemCount)
if visibleItemRange.maxIndex >= visibleItemRange.minIndex {
for i in visibleItemRange.minIndex ... visibleItemRange.maxIndex {
let participant = members.participants[i]
validItemIds.append(participant.peer.id)
var itemTransition = transition
let itemView: ComponentView<Empty>
if let current = self.itemViews[participant.peer.id] {
itemView = current
} else {
itemTransition = itemTransition.withAnimation(.none)
itemView = ComponentView()
self.itemViews[participant.peer.id] = itemView
}
let itemFrame = itemLayout.frame(at: i)
let _ = itemView.update(
transition: itemTransition,
component: AnyComponent(ParticipantVideoComponent(
call: component.call,
participant: participant
)),
environment: {},
containerSize: itemFrame.size
)
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
self.scrollView.addSubview(itemComponentView)
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
}
}
}
var removedItemIds: [EnginePeer.Id] = []
for (itemId, itemView) in self.itemViews {
if !validItemIds.contains(itemId) {
removedItemIds.append(itemId)
if let itemComponentView = itemView.view {
itemComponentView.removeFromSuperview()
}
}
}
for itemId in removedItemIds {
self.itemViews.removeValue(forKey: itemId)
}
}
func update(component: VideoChatParticipantsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component
let itemLayout = ItemLayout(containerSize: availableSize, itemCount: component.members?.totalCount ?? 0)
self.itemLayout = itemLayout
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
if let members = component.members {
for participant in members.participants {
if let videoChannel = participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .medium) {
requestedVideo.append(videoChannel)
}
}
}
(component.call as! PresentationGroupCallImpl).setRequestedVideoList(items: requestedVideo)
self.ignoreScrolling = true
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
let contentSize = CGSize(width: availableSize.width, height: itemLayout.contentHeight())
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,219 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import ViewControllerComponent
import Postbox
import TelegramCore
import AccountContext
import PlainButtonComponent
import SwiftSignalKit
private final class VideoChatScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let call: PresentationGroupCall
init(
call: PresentationGroupCall
) {
self.call = call
}
static func ==(lhs: VideoChatScreenComponent, rhs: VideoChatScreenComponent) -> Bool {
return true
}
final class View: UIView {
private var component: VideoChatScreenComponent?
private var environment: ViewControllerComponentContainer.Environment?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private let closeButton = ComponentView<Empty>()
private let participants = ComponentView<Empty>()
private var members: PresentationGroupCallMembers?
private var membersDisposable: Disposable?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.membersDisposable?.dispose()
}
func update(component: VideoChatScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let environment = environment[ViewControllerComponentContainer.Environment.self].value
let themeUpdated = self.environment?.theme !== environment.theme
if self.component == nil {
self.membersDisposable = (component.call.members
|> deliverOnMainQueue).startStrict(next: { [weak self] members in
guard let self else {
return
}
if self.members != members {
self.members = members
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
}
})
}
self.component = component
self.environment = environment
self.state = state
if themeUpdated {
self.backgroundColor = .black
}
let closeButtonSize = self.closeButton.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(Text(
text: "Leave", font: Font.regular(16.0), color: environment.theme.list.itemDestructiveColor)),
effectAlignment: .center,
minSize: CGSize(width: 44.0, height: 44.0),
contentInsets: UIEdgeInsets(),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
let _ = component.call.leave(terminateIfPossible: false).startStandalone()
if let controller = self.environment?.controller() {
controller.dismiss()
}
},
animateAlpha: true,
animateScale: true,
animateContents: false
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - closeButtonSize.width, y: availableSize.height - environment.safeInsets.bottom - 16.0 - closeButtonSize.height), size: closeButtonSize)
if let closeButtonView = self.closeButton.view {
if closeButtonView.superview == nil {
self.addSubview(closeButtonView)
}
transition.setFrame(view: closeButtonView, frame: closeButtonFrame)
}
let participantsSize = self.participants.update(
transition: transition,
component: AnyComponent(VideoChatParticipantsComponent(
call: component.call,
members: self.members
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: closeButtonFrame.minY - environment.statusBarHeight)
)
let participantsFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.statusBarHeight), size: participantsSize)
if let participantsView = self.participants.view {
if participantsView.superview == nil {
self.addSubview(participantsView)
}
transition.setFrame(view: participantsView, frame: participantsFrame)
}
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatController {
public let call: PresentationGroupCall
public var currentOverlayController: VoiceChatOverlayController?
public var parentNavigationController: NavigationController?
public var onViewDidAppear: (() -> Void)?
public var onViewDidDisappear: (() -> Void)?
private var isDismissed: Bool = false
private var didAppearOnce: Bool = false
private var idleTimerExtensionDisposable: Disposable?
public init(
call: PresentationGroupCall
) {
self.call = call
super.init(
context: call.accountContext,
component: VideoChatScreenComponent(
call: call
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
presentationMode: .default,
theme: .dark
)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.idleTimerExtensionDisposable?.dispose()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.isDismissed = false
if !self.didAppearOnce {
self.didAppearOnce = true
//self.controllerNode.animateIn()
self.idleTimerExtensionDisposable?.dispose()
self.idleTimerExtensionDisposable = self.call.accountContext.sharedContext.applicationBindings.pushIdleTimerExtension()
}
self.onViewDidAppear?()
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.idleTimerExtensionDisposable?.dispose()
self.idleTimerExtensionDisposable = nil
self.didAppearOnce = false
self.isDismissed = true
self.onViewDidDisappear?()
}
public func dismiss(closing: Bool, manual: Bool) {
self.dismiss()
}
}

View File

@ -245,11 +245,13 @@ public protocol VoiceChatController: ViewController {
var call: PresentationGroupCall { get } var call: PresentationGroupCall { get }
var currentOverlayController: VoiceChatOverlayController? { get } var currentOverlayController: VoiceChatOverlayController? { get }
var parentNavigationController: NavigationController? { get set } var parentNavigationController: NavigationController? { get set }
var onViewDidAppear: (() -> Void)? { get set }
var onViewDidDisappear: (() -> Void)? { get set }
func dismiss(closing: Bool, manual: Bool) func dismiss(closing: Bool, manual: Bool)
} }
public final class VoiceChatControllerImpl: ViewController, VoiceChatController { final class VoiceChatControllerImpl: ViewController, VoiceChatController {
enum DisplayMode { enum DisplayMode {
case modal(isExpanded: Bool, isFilled: Bool) case modal(isExpanded: Bool, isFilled: Bool)
case fullscreen(controlsHidden: Bool) case fullscreen(controlsHidden: Bool)
@ -7094,3 +7096,11 @@ private final class VoiceChatContextReferenceContentSource: ContextReferenceCont
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
} }
} }
public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall) -> VoiceChatController {
if sharedContext.immediateExperimentalUISettings.callV2 {
return VideoChatScreenV2Impl(call: call)
} else {
return VoiceChatControllerImpl(sharedContext: sharedContext, accountContext: accountContext, call: call)
}
}

View File

@ -172,9 +172,10 @@ public func updateMessageReactionsInteractively(account: Account, messageIds: [M
|> ignoreValues |> ignoreValues
} }
public func sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int, isAnonymous: Bool?) -> Signal<Never, NoError> { func _internal_sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int, isAnonymous: Bool?) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Void in return account.postbox.transaction { transaction -> Bool in
transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: SendStarsReactionsAction(randomId: Int64.random(in: Int64.min ... Int64.max))) transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: SendStarsReactionsAction(randomId: Int64.random(in: Int64.min ... Int64.max)))
var resolvedIsAnonymousValue = false
transaction.updateMessage(messageId, update: { currentMessage in transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo? var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo { if let forwardInfo = currentMessage.forwardInfo {
@ -206,10 +207,13 @@ public func sendStarsReactionsInteractively(account: Account, messageId: Message
attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: resolvedIsAnonymous)) attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: resolvedIsAnonymous))
resolvedIsAnonymousValue = resolvedIsAnonymous
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
}) })
return resolvedIsAnonymousValue
} }
|> ignoreValues
} }
func cancelPendingSendStarsReactionInteractively(account: Account, messageId: MessageId) -> Signal<Never, NoError> { func cancelPendingSendStarsReactionInteractively(account: Account, messageId: MessageId) -> Signal<Never, NoError> {

View File

@ -334,8 +334,8 @@ public extension TelegramEngine {
).startStandalone() ).startStandalone()
} }
public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool?) { public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool?) -> Signal<Bool, NoError> {
let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count, isAnonymous: isAnonymous).startStandalone() return _internal_sendStarsReactionsInteractively(account: self.account, messageId: id, count: count, isAnonymous: isAnonymous)
} }
public func cancelPendingSendStarsReaction(id: EngineMessage.Id) { public func cancelPendingSendStarsReaction(id: EngineMessage.Id) {

View File

@ -191,7 +191,7 @@ final class PrivateCallPictureInPictureView: UIView {
} }
if let videoMetrics = self.videoMetrics { if let videoMetrics = self.videoMetrics {
let resolvedRotationAngle = resolveVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: UIApplication.shared.statusBarOrientation) let resolvedRotationAngle = resolveCallVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: UIApplication.shared.statusBarOrientation)
var rotatedResolution = videoMetrics.resolution var rotatedResolution = videoMetrics.resolution
var videoIsRotated = false var videoIsRotated = false

View File

@ -9,7 +9,7 @@ private let shadowImage: UIImage? = {
UIImage(named: "Call/VideoGradient")?.precomposed() UIImage(named: "Call/VideoGradient")?.precomposed()
}() }()
func resolveVideoRotationAngle(angle: Float, followsDeviceOrientation: Bool, interfaceOrientation: UIInterfaceOrientation) -> Float { public func resolveCallVideoRotationAngle(angle: Float, followsDeviceOrientation: Bool, interfaceOrientation: UIInterfaceOrientation) -> Float {
if !followsDeviceOrientation { if !followsDeviceOrientation {
return angle return angle
} }
@ -408,7 +408,7 @@ final class VideoContainerView: HighlightTrackingButton {
self.dragPositionAnimatorLink = nil self.dragPositionAnimatorLink = nil
return return
} }
let videoLayout = self.calculateMinimizedLayout(params: params, videoMetrics: videoMetrics, resolvedRotationAngle: resolveVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation), applyDragPosition: false) let videoLayout = self.calculateMinimizedLayout(params: params, videoMetrics: videoMetrics, resolvedRotationAngle: resolveCallVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation), applyDragPosition: false)
let targetPosition = videoLayout.rotatedVideoFrame.center let targetPosition = videoLayout.rotatedVideoFrame.center
self.dragVelocity = self.updateVelocityUsingSpring( self.dragVelocity = self.updateVelocityUsingSpring(
@ -558,7 +558,7 @@ final class VideoContainerView: HighlightTrackingButton {
} }
self.appliedVideoMetrics = videoMetrics self.appliedVideoMetrics = videoMetrics
let resolvedRotationAngle = resolveVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation) let resolvedRotationAngle = resolveCallVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation)
if params.isMinimized { if params.isMinimized {
self.isFillingBounds = false self.isFillingBounds = false
@ -588,7 +588,7 @@ final class VideoContainerView: HighlightTrackingButton {
if let disappearingVideoLayer = self.disappearingVideoLayer { if let disappearingVideoLayer = self.disappearingVideoLayer {
self.disappearingVideoLayer = nil self.disappearingVideoLayer = nil
let disappearingVideoLayout = self.calculateMinimizedLayout(params: params, videoMetrics: disappearingVideoLayer.videoMetrics, resolvedRotationAngle: resolveVideoRotationAngle(angle: disappearingVideoLayer.videoMetrics.rotationAngle, followsDeviceOrientation: disappearingVideoLayer.videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation), applyDragPosition: true) let disappearingVideoLayout = self.calculateMinimizedLayout(params: params, videoMetrics: disappearingVideoLayer.videoMetrics, resolvedRotationAngle: resolveCallVideoRotationAngle(angle: disappearingVideoLayer.videoMetrics.rotationAngle, followsDeviceOrientation: disappearingVideoLayer.videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation), applyDragPosition: true)
let initialDisappearingVideoSize = disappearingVideoLayout.effectiveVideoFrame.size let initialDisappearingVideoSize = disappearingVideoLayout.effectiveVideoFrame.size
if !disappearingVideoLayer.isAlphaAnimationInitiated { if !disappearingVideoLayer.isAlphaAnimationInitiated {

View File

@ -4464,6 +4464,23 @@ public final class StoryItemSetContainerComponent: Component {
return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation) return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation)
} }
var selectedItems: Set<AnyHashable> = Set()
if let myReaction = component.slice.item.storyItem.myReaction {
switch myReaction {
case .builtin, .stars:
if let availableReactions = component.availableReactions {
for availableReaction in availableReactions.reactionItems {
if availableReaction.reaction.rawValue == myReaction {
selectedItems.insert(AnyHashable(availableReaction.stillAnimation.fileId))
break
}
}
}
case let .custom(fileId):
selectedItems.insert(AnyHashable(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)))
}
}
return EmojiPagerContentComponent.emojiInputData( return EmojiPagerContentComponent.emojiInputData(
context: component.context, context: component.context,
animationCache: animationCache, animationCache: animationCache,
@ -4475,7 +4492,7 @@ public final class StoryItemSetContainerComponent: Component {
areUnicodeEmojiEnabled: false, areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true, areCustomEmojiEnabled: true,
chatPeerId: component.context.account.peerId, chatPeerId: component.context.account.peerId,
selectedItems: Set(), selectedItems: selectedItems,
premiumIfSavedMessages: false premiumIfSavedMessages: false
) )
}, },

View File

@ -441,8 +441,13 @@ extension ChatControllerImpl {
return return
} }
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil) let _ = (strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil)
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) |> deliverOnMainQueue).startStandalone(next: { isAnonymous in
guard let strongSelf = self else {
return
}
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1, isAnonymous: isAnonymous)
})
}) })
} else { } else {
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction

View File

@ -1744,8 +1744,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil) let _ = (strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil)
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) |> deliverOnMainQueue).startStandalone(next: { isAnonymous in
guard let strongSelf = self else {
return
}
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1, isAnonymous: isAnonymous)
})
}) })
} else { } else {
var removedReaction: MessageReaction.Reaction? var removedReaction: MessageReaction.Reaction?

View File

@ -317,7 +317,7 @@ extension ChatControllerImpl {
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in |> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
if case let .result(info, items, _) = result, let presentationContext = presentationContext { if case let .result(info, items, _) = result, let presentationContext = presentationContext {
let tip: ContextController.Tip = .animatedEmoji( let tip: ContextController.Tip = .animatedEmoji(
text: presentationData.strings.ChatContextMenu_ReactionEmojiSetSingle(info.title).string, text: presentationData.strings.ChatContextMenu_SingleReactionEmojiSet(info.title).string,
arguments: TextNodeWithEntities.Arguments( arguments: TextNodeWithEntities.Arguments(
context: context, context: context,
cache: presentationContext.animationCache, cache: presentationContext.animationCache,
@ -485,14 +485,14 @@ extension ChatControllerImpl {
} }
} }
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous) let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous).startStandalone()
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount)) self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount), isAnonymous: isAnonymous)
})) }))
}) })
}) })
} }
func displayOrUpdateSendStarsUndo(messageId: EngineMessage.Id, count: Int) { func displayOrUpdateSendStarsUndo(messageId: EngineMessage.Id, count: Int, isAnonymous: Bool) {
if self.currentSendStarsUndoMessageId != messageId { if self.currentSendStarsUndoMessageId != messageId {
if let current = self.currentSendStarsUndoController { if let current = self.currentSendStarsUndoController {
self.currentSendStarsUndoController = nil self.currentSendStarsUndoController = nil
@ -506,7 +506,12 @@ extension ChatControllerImpl {
self.currentSendStarsUndoCount = count self.currentSendStarsUndoCount = count
} }
let title: String = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount)) let title: String
if isAnonymous {
title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount))
} else {
title = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount))
}
let textItems = extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ let textItems = extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [
0: .number(self.currentSendStarsUndoCount, minDigits: 1), 0: .number(self.currentSendStarsUndoCount, minDigits: 1),

View File

@ -907,7 +907,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
navigationController.pushViewController(groupCallController) navigationController.pushViewController(groupCallController)
} else { } else {
strongSelf.hasGroupCallOnScreenPromise.set(true) strongSelf.hasGroupCallOnScreenPromise.set(true)
let groupCallController = VoiceChatControllerImpl(sharedContext: strongSelf, accountContext: call.accountContext, call: call) let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
groupCallController.onViewDidAppear = { [weak self] in groupCallController.onViewDidAppear = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.hasGroupCallOnScreenPromise.set(true) strongSelf.hasGroupCallOnScreenPromise.set(true)