Merge commit 'dfe1dbd825ea321903a7d63e19577109c2cfa35b'

This commit is contained in:
Ali 2021-04-05 20:01:19 +04:00
commit 5d3e3b9ebe
8 changed files with 4977 additions and 4791 deletions

View File

@ -6339,3 +6339,25 @@ Sorry for the inconvenience.";
"VoiceChat.UnpinVideo" = "Unpin Video"; "VoiceChat.UnpinVideo" = "Unpin Video";
"Notification.VoiceChatScheduled" = "Voice chat scheduled"; "Notification.VoiceChatScheduled" = "Voice chat scheduled";
"VoiceChat.EditStartTime" = "Edit Start Time";
"VoiceChat.StartsIn" = "Starts in";
"VoiceChat.SetReminder" = "Set Reminder";
"VoiceChat.CancelReminder" = "Cancel Reminder";
"VoiceChat.ShareShort" = "share";
"ChannelInfo.ScheduleVoiceChat" = "Schedule Voice Chat";
"ScheduleVoiceChat.Title" = "Schedule Voice Chat";
"ScheduleVoiceChat.GroupText" = "The members of the group will be notified that the voice chat will start in %@.";
"ScheduleVoiceChat.ChannelText" = "The members of the channel will be notified that the voice chat will start in %@.";
"ScheduleVoiceChat.ScheduleToday" = "Remind today at %@";
"ScheduleVoiceChat.ScheduleTomorrow" = "Remind tomorrow at %@";
"ScheduleVoiceChat.ScheduleOn" = "Remind on %@ at %@";
"Conversation.ScheduledVoiceChat" = "Scheduled Voice Chat";
"Conversation.ScheduledVoiceChatStartsInShort" = "Voice chat starts %@";
"Conversation.ScheduledVoiceChatStartsInShort" = "Starts %@";

View File

@ -12,8 +12,10 @@ import AlertUI
import PresentationDataUtils import PresentationDataUtils
import CountrySelectionUI import CountrySelectionUI
import PhoneNumberFormat import PhoneNumberFormat
import CoreTelephony
import MessageUI
final class ChangePhoneNumberController: ViewController { final class ChangePhoneNumberController: ViewController, MFMailComposeViewControllerDelegate {
private var controllerNode: ChangePhoneNumberControllerNode { private var controllerNode: ChangePhoneNumberControllerNode {
return self.displayNode as! ChangePhoneNumberControllerNode return self.displayNode as! ChangePhoneNumberControllerNode
} }
@ -133,6 +135,9 @@ final class ChangePhoneNumberController: ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String let text: String
var actions: [TextAlertAction] = [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]
switch error { switch error {
case .limitExceeded: case .limitExceeded:
text = presentationData.strings.Login_CodeFloodError text = presentationData.strings.Login_CodeFloodError
@ -140,11 +145,26 @@ final class ChangePhoneNumberController: ViewController {
text = presentationData.strings.Login_InvalidPhoneError text = presentationData.strings.Login_InvalidPhoneError
case .phoneNumberOccupied: case .phoneNumberOccupied:
text = presentationData.strings.ChangePhone_ErrorOccupied(formatPhoneNumber(phoneNumber)).0 text = presentationData.strings.ChangePhone_ErrorOccupied(formatPhoneNumber(phoneNumber)).0
case .phoneBanned:
text = presentationData.strings.Login_PhoneBannedError
actions.append(TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_PhoneNumberHelp, action: { [weak self] in
guard let strongSelf = self else {
return
}
let formattedNumber = formatPhoneNumber(number)
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
let systemVersion = UIDevice.current.systemVersion
let locale = Locale.current.identifier
let carrier = CTCarrier()
let mnc = carrier.mobileNetworkCode ?? "none"
strongSelf.presentEmailComposeController(address: "login@stel.com", subject: presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).0, body: presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).0)
}))
case .generic: case .generic:
text = presentationData.strings.Login_UnknownError text = presentationData.strings.Login_UnknownError
} }
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions), in: .window(.root))
} }
})) }))
} else { } else {
@ -152,4 +172,22 @@ final class ChangePhoneNumberController: ViewController {
self.controllerNode.animateError() self.controllerNode.animateError()
} }
} }
private func presentEmailComposeController(address: String, subject: String, body: String) {
if MFMailComposeViewController.canSendMail() {
let composeController = MFMailComposeViewController()
composeController.setToRecipients([address])
composeController.setSubject(subject)
composeController.setMessageBody(body, isHTML: false)
composeController.mailComposeDelegate = self
self.view.window?.rootViewController?.present(composeController, animated: true, completion: nil)
} else {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
} }

View File

@ -1322,7 +1322,7 @@ public final class VoiceChatController: ViewController {
maxBioLength = 100 maxBioLength = 100
} }
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, maxLength: maxBioLength, apply: { bio in let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, maxLength: maxBioLength, apply: { bio in
if let strongSelf = self { if let strongSelf = self, let bio = bio {
let _ = (updateAbout(account: strongSelf.context.account, about: bio) let _ = (updateAbout(account: strongSelf.context.account, about: bio)
|> `catch` { _ -> Signal<Void, NoError> in |> `catch` { _ -> Signal<Void, NoError> in
return .complete() return .complete()
@ -1345,8 +1345,8 @@ public final class VoiceChatController: ViewController {
f(.default) f(.default)
Queue.mainQueue().after(0.1) { Queue.mainQueue().after(0.1) {
let controller = voiceChatUserNameController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: presentationData.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: presentationData.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { firstName, lastName in let controller = voiceChatUserNameController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: presentationData.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: presentationData.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { firstAndLastName in
if let strongSelf = self { if let strongSelf = self, let (firstName, lastName) = firstAndLastName {
let _ = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName).start() let _ = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName).start()
strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false }) strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false })
@ -1994,6 +1994,15 @@ public final class VoiceChatController: ViewController {
c.setItems(strongSelf.contextMenuPermissionItems()) c.setItems(strongSelf.contextMenuPermissionItems())
}))) })))
} }
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EditPermissions, icon: { theme -> UIImage? in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor)
}, action: { c, _ in
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuPermissionItems())
})))
if let inviteLinks = inviteLinks { if let inviteLinks = inviteLinks {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
@ -3748,7 +3757,7 @@ public final class VoiceChatController: ViewController {
mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in
if let image = image, let asset = asset { if let image = image, let asset = asset {
completion() completion()
// self?.updateProfileVideo(image, asset: asset, adjustments: adjustments) self?.updateProfileVideo(image, asset: asset, adjustments: adjustments)
} }
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
@ -3756,55 +3765,42 @@ public final class VoiceChatController: ViewController {
return return
} }
// let proceed = { let proceed = {
// if let item = item { let _ = strongSelf.currentAvatarMixin.swap(nil)
// strongSelf.deleteAvatar(item, remove: false) let postbox = strongSelf.context.account.postbox
// } strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
// return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
// let _ = strongSelf.currentAvatarMixin.swap(nil) })
// if let _ = peer.smallProfileImage { |> deliverOnMainQueue).start(next: { result in
// strongSelf.state = strongSelf.state.withUpdatingAvatar(nil) guard let strongSelf = self else {
// if let (layout, navigationHeight) = strongSelf.validLayout { return
// strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) }
// } switch result {
// } case .complete:
// let postbox = strongSelf.context.account.postbox break
// strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in case .progress:
// return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) break
// }) }
// |> deliverOnMainQueue).start(next: { result in }))
// guard let strongSelf = self else { }
// return
// } let actionSheet = ActionSheetController(presentationData: presentationData)
// switch result { let items: [ActionSheetItem] = [
// case .complete: ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, color: .destructive, action: { [weak actionSheet] in
// strongSelf.state = strongSelf.state.withUpdatingAvatar(nil) actionSheet?.dismissAnimated()
// if let (layout, navigationHeight) = strongSelf.validLayout { proceed()
// strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) })
// } ]
// case .progress:
// break actionSheet.setItemGroups([
// } ActionSheetItemGroup(items: items),
// })) ActionSheetItemGroup(items: [
// } ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// let actionSheet = ActionSheetController(presentationData: presentationData) })
// let items: [ActionSheetItem] = [ ])
// ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, color: .destructive, action: { [weak actionSheet] in ])
// actionSheet?.dismissAnimated() strongSelf.controller?.present(actionSheet, in: .window(.root))
// proceed()
// })
// ]
//
// actionSheet.setItemGroups([
// ActionSheetItemGroup(items: items),
// ActionSheetItemGroup(items: [
// ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// })
// ])
// ])
// strongSelf.controller?.present(actionSheet, in: .window(.root))
} }
mixin.didDismiss = { [weak legacyController] in mixin.didDismiss = { [weak legacyController] in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -3856,6 +3852,125 @@ public final class VoiceChatController: ViewController {
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set()) self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
} }
private func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?) {
guard let data = image.jpegData(compressionQuality: 0.6), let peerId = self.callState?.myPeerId else {
return
}
let photoResource = LocalFileMediaResource(fileId: arc4random64())
self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil)
self.currentUpdatingAvatar = representation
self.updateAvatarPromise.set(.single((representation, 0.0)))
var videoStartTimestamp: Double? = nil
if let adjustments = adjustments, adjustments.videoStartValue > 0.0 {
videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue
}
let account = self.context.account
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { [weak self] subscriber in
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
} else {
return nil
}
}
let uploadInterface = LegacyLiveUploadInterface(account: account)
let signal: SSignal
if let asset = asset as? AVAsset {
signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
} else if let url = asset as? URL, let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer {
let durationSignal: SSignal = SSignal(generator: { subscriber in
let disposable = (entityRenderer.duration()).start(next: { duration in
subscriber?.putNext(duration)
subscriber?.putCompletion()
})
return SBlockDisposable(block: {
disposable.dispose()
})
})
signal = durationSignal.map(toSignal: { duration -> SSignal? in
if let duration = duration as? Double {
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, watcher: nil, entityRenderer: entityRenderer)!
} else {
return SSignal.single(nil)
}
})
} else {
signal = SSignal.complete()
}
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
if let timestamp = videoStartTimestamp {
videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05))
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
let resource: TelegramMediaResource
if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
resource = LocalFileMediaResource(fileId: liveUploadData.id)
} else {
resource = LocalFileMediaResource(fileId: arc4random64())
}
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
}
}
subscriber.putCompletion()
} else if let strongSelf = self, let progress = next as? NSNumber {
Queue.mainQueue().async {
strongSelf.updateAvatarPromise.set(.single((representation, Float(truncating: progress) * 0.25)))
}
}
}, error: { _ in
}, completed: nil)
let disposable = ActionDisposable {
signalDisposable?.dispose()
}
return ActionDisposable {
disposable.dispose()
}
}
self.updateAvatarDisposable.set((signal
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if peerId.namespace == Namespaces.Peer.CloudUser {
return updateAccountPhoto(account: account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
} else {
return updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: photoResource), video: uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
}
}
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
switch result {
case .complete:
strongSelf.updateAvatarPromise.set(.single(nil))
case let .progress(value):
strongSelf.updateAvatarPromise.set(.single((representation, 0.25 + value * 0.75)))
}
}))
}
} }
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext

View File

@ -710,7 +710,7 @@ private final class VoiceChatUserNameEditAlertContentNode: AlertContentNode {
} }
} }
func voiceChatUserNameController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, firstNamePlaceholder: String, lastNamePlaceholder: String, doneButtonTitle: String? = nil, firstName: String?, lastName: String?, maxLength: Int, apply: @escaping (String, String) -> Void) -> AlertController { func voiceChatUserNameController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, firstNamePlaceholder: String, lastNamePlaceholder: String, doneButtonTitle: String? = nil, firstName: String?, lastName: String?, maxLength: Int, apply: @escaping ((String, String)?) -> Void) -> AlertController {
var presentationData = sharedContext.currentPresentationData.with { $0 } var presentationData = sharedContext.currentPresentationData.with { $0 }
if let forceTheme = forceTheme { if let forceTheme = forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme) presentationData = presentationData.withUpdated(theme: forceTheme)
@ -733,14 +733,24 @@ func voiceChatUserNameController(sharedContext: SharedAccountContext, account: A
guard let contentNode = contentNode else { guard let contentNode = contentNode else {
return return
} }
dismissImpl?(true)
let previousFirstName = firstName ?? "" let previousFirstName = firstName ?? ""
let previousLastName = firstName ?? "" let previousLastName = lastName ?? ""
let newFirstName = contentNode.firstName.trimmingCharacters(in: .whitespacesAndNewlines) let newFirstName = contentNode.firstName.trimmingCharacters(in: .whitespacesAndNewlines)
let newLastName = contentNode.lastName.trimmingCharacters(in: .whitespacesAndNewlines) let newLastName = contentNode.lastName.trimmingCharacters(in: .whitespacesAndNewlines)
apply(newFirstName, newLastName)
if newFirstName.isEmpty {
contentNode.animateError()
return
}
dismissImpl?(true)
if previousFirstName != newFirstName || previousLastName != newLastName {
apply((newFirstName, newLastName))
} else {
apply(nil)
}
} }
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)

View File

@ -33,6 +33,7 @@ public enum RequestChangeAccountPhoneNumberVerificationError {
case invalidPhoneNumber case invalidPhoneNumber
case limitExceeded case limitExceeded
case phoneNumberOccupied case phoneNumberOccupied
case phoneBanned
case generic case generic
} }
@ -45,6 +46,8 @@ public func requestChangeAccountPhoneNumberVerification(account: Account, phoneN
return .invalidPhoneNumber return .invalidPhoneNumber
} else if error.errorDescription == "PHONE_NUMBER_OCCUPIED" { } else if error.errorDescription == "PHONE_NUMBER_OCCUPIED" {
return .phoneNumberOccupied return .phoneNumberOccupied
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
return .phoneBanned
} else { } else {
return .generic return .generic
} }

View File

@ -54,7 +54,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
let referenceNode: ContextReferenceContentNode let referenceNode: ContextReferenceContentNode
let containerNode: ContextControllerSourceNode let containerNode: ContextControllerSourceNode
private let backgroundNode: ASImageNode private let backgroundNode: ASImageNode
private let backgroundWithIconNode: ASImageNode private let iconNode: ASImageNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private var animationNode: AnimationNode? private var animationNode: AnimationNode?
@ -73,11 +73,10 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
self.backgroundNode = ASImageNode() self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.isHidden = true
self.backgroundWithIconNode = ASImageNode() self.iconNode = ASImageNode()
self.backgroundWithIconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.backgroundWithIconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
@ -88,7 +87,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
self.containerNode.addSubnode(self.referenceNode) self.containerNode.addSubnode(self.referenceNode)
self.referenceNode.addSubnode(self.backgroundNode) self.referenceNode.addSubnode(self.backgroundNode)
self.referenceNode.addSubnode(self.backgroundWithIconNode) self.referenceNode.addSubnode(self.iconNode)
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
@ -126,13 +125,12 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
let previousIcon = self.icon let previousIcon = self.icon
let iconUpdated = self.icon != icon let iconUpdated = self.icon != icon
if self.theme != presentationData.theme || self.icon != icon || self.isActive != isActive { let isActiveUpdated = self.isActive != isActive
self.isActive = isActive
if self.theme != presentationData.theme || self.icon != icon {
self.theme = presentationData.theme self.theme = presentationData.theme
self.icon = icon self.icon = icon
let isActiveUpdated = self.isActive != isActive
self.isActive = isActive
var isGestureEnabled = false var isGestureEnabled = false
if [.mute, .voiceChat, .more].contains(icon) { if [.mute, .voiceChat, .more].contains(icon) {
isGestureEnabled = true isGestureEnabled = true
@ -195,17 +193,13 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
} }
} else { } else {
animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0) animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0)
self.addSubnode(animationNode) self.referenceNode.addSubnode(animationNode)
self.animationNode = animationNode self.animationNode = animationNode
} }
animationNode.frame = CGRect(origin: CGPoint(), size: size) animationNode.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundWithIconNode.isHidden = true
self.backgroundNode.isHidden = false
} else if let animationNode = self.animationNode { } else if let animationNode = self.animationNode {
self.animationNode = nil self.animationNode = nil
animationNode.removeFromSupernode() animationNode.removeFromSupernode()
self.backgroundWithIconNode.isHidden = false
self.backgroundNode.isHidden = true
} }
if playOnce { if playOnce {
@ -213,35 +207,10 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
} else if seekToEnd { } else if seekToEnd {
self.animationNode?.seekToEnd() self.animationNode?.seekToEnd()
} }
if isActiveUpdated, !self.containerNode.alpha.isZero { self.backgroundNode.image = generateFilledCircleImage(diameter: 40.0, color: presentationData.theme.list.itemAccentColor)
let backgroundNode = !self.backgroundNode.isHidden ? self.backgroundNode : self.backgroundWithIconNode self.iconNode.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
if let snapshotView = backgroundNode.view.snapshotContentTree() {
snapshotView.frame = backgroundNode.view.frame
if let animationNode = self.animationNode {
self.view.insertSubview(snapshotView, belowSubview: animationNode.view)
} else {
self.view.addSubview(snapshotView)
}
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
if !isExpanded, let snapshotView = self.textNode.view.snapshotContentTree() {
snapshotView.frame = self.textNode.view.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()
})
}
}
self.backgroundNode.image = generateFilledCircleImage(diameter: 40.0, color: isActive ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemDisabledTextColor)
self.backgroundWithIconNode.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(isActive ? presentationData.theme.list.itemAccentColor.cgColor : presentationData.theme.list.itemDisabledTextColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.normal) context.setBlendMode(.normal)
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor) context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
let imageName: String? let imageName: String?
@ -275,15 +244,22 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
}) })
} }
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(12.0), textColor: isActive ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemDisabledTextColor) let alpha: CGFloat = isActive ? 1.0 : 0.3
if isActiveUpdated, !self.containerNode.alpha.isZero {
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.backgroundNode, alpha: isActive ? 1.0 : 0.3)
alphaTransition.updateAlpha(node: self.textNode, alpha: isActive ? 1.0 : 0.3)
}
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(12.0), textColor: presentationData.theme.list.itemAccentColor)
self.accessibilityLabel = text self.accessibilityLabel = text
let titleSize = self.textNode.updateLayout(CGSize(width: 120.0, height: .greatestFiniteMagnitude)) let titleSize = self.textNode.updateLayout(CGSize(width: 120.0, height: .greatestFiniteMagnitude))
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.backgroundWithIconNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: size.height + 6.0), size: titleSize)) transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: size.height + 6.0), size: titleSize))
transition.updateAlpha(node: self.textNode, alpha: isExpanded ? 0.0 : 1.0) transition.updateAlpha(node: self.textNode, alpha: isExpanded ? 0.0 : alpha)
self.referenceNode.frame = self.containerNode.bounds self.referenceNode.frame = self.containerNode.bounds
} }