mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Various improvements
This commit is contained in:
@@ -15148,3 +15148,5 @@ Error: %8$@";
|
||||
"ProfileColorSetup.NoProfileGiftsPlaceholder" = "You don't have any gifts you can use as styles for your profile.";
|
||||
"ProfileColorSetup.NoNameGiftsPlaceholder" = "You don't have any gifts you can use as styles for your name.";
|
||||
"ProfileColorSetup.BrowseGiftsForPurchase" = "Browse gifts available for purchase >";
|
||||
|
||||
"VoiceChat.RemovedConferencePeerText" = "You removed %@ from this call. They will no longer be able to join using the invite link.";
|
||||
|
||||
@@ -186,6 +186,20 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func attributes(at point: CGPoint) -> (Int, [NSAttributedString.Key: Any])? {
|
||||
if let result = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.textNode.frame.minX, y: point.y - self.textNode.frame.minY)) {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public var isSpoilerConcealed: Bool {
|
||||
if let dustNode = self.textNode.dustNode, !dustNode.isRevealed {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func updateVisibility(_ isVisible: Bool) {
|
||||
self.textNode.visibility = isVisible
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import TextFormat
|
||||
import TelegramPresentationData
|
||||
import ReactionSelectionNode
|
||||
import BundleIconComponent
|
||||
import LottieComponent
|
||||
import Markdown
|
||||
|
||||
private let glassColor = UIColor(rgb: 0x25272e, alpha: 0.72)
|
||||
@@ -22,6 +23,7 @@ final class MessageItemComponent: Component {
|
||||
public enum Icon: Equatable {
|
||||
case peer(EnginePeer)
|
||||
case icon(String)
|
||||
case animation(String)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
@@ -30,7 +32,7 @@ final class MessageItemComponent: Component {
|
||||
private let text: String
|
||||
private let entities: [MessageTextEntity]
|
||||
private let availableReactions: [ReactionItem]?
|
||||
private let avatarTapped: () -> Void
|
||||
private let openPeer: ((EnginePeer) -> Void)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@@ -39,7 +41,7 @@ final class MessageItemComponent: Component {
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
availableReactions: [ReactionItem]?,
|
||||
avatarTapped: @escaping () -> Void = {}
|
||||
openPeer: ((EnginePeer) -> Void)?
|
||||
) {
|
||||
self.context = context
|
||||
self.icon = icon
|
||||
@@ -47,7 +49,7 @@ final class MessageItemComponent: Component {
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.availableReactions = availableReactions
|
||||
self.avatarTapped = avatarTapped
|
||||
self.openPeer = openPeer
|
||||
}
|
||||
|
||||
static func == (lhs: MessageItemComponent, rhs: MessageItemComponent) -> Bool {
|
||||
@@ -77,6 +79,9 @@ final class MessageItemComponent: Component {
|
||||
private let text: ComponentView<Empty>
|
||||
weak var standaloneReactionAnimation: StandaloneReactionAnimation?
|
||||
|
||||
private var cachedEntities: [MessageTextEntity]?
|
||||
private var entityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||
|
||||
private var component: MessageItemComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
@@ -95,12 +100,21 @@ final class MessageItemComponent: Component {
|
||||
self.addSubview(self.container)
|
||||
self.container.addSubview(self.background)
|
||||
self.container.addSubview(self.avatarNode.view)
|
||||
|
||||
self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.avatarTapped)))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func avatarTapped() {
|
||||
guard let component = self.component, case let .peer(peer) = component.icon else {
|
||||
return
|
||||
}
|
||||
component.openPeer?(peer)
|
||||
}
|
||||
|
||||
func animateFrom(globalFrame: CGRect, cornerRadius: CGFloat, textSnapshotView: UIView, transition: ComponentTransition) {
|
||||
guard let superview = self.superview?.superview?.superview else {
|
||||
return
|
||||
@@ -143,8 +157,17 @@ final class MessageItemComponent: Component {
|
||||
transition.animateScale(view: self.avatarNode.view, from: 0.01, to: 1.0)
|
||||
}
|
||||
|
||||
private var cachedEntities: [MessageTextEntity]?
|
||||
private var entityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if !self.avatarNode.isHidden, self.avatarNode.frame.contains(point) {
|
||||
return true
|
||||
}
|
||||
if let textView = self.text.view as? MultilineTextWithEntitiesComponent.View, let (_, attributes) = textView.attributes(at: self.convert(point, to: textView)) {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], textView.isSpoilerConcealed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func update(component: MessageItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let isFirstTime = self.component == nil
|
||||
@@ -170,38 +193,9 @@ final class MessageItemComponent: Component {
|
||||
let avatarSize = CGSize(width: component.isNotification ? 30.0 : 28.0, height: component.isNotification ? 30.0 : 28.0)
|
||||
let avatarSpacing: CGFloat = 10.0
|
||||
let iconSpacing: CGFloat = 10.0
|
||||
let rightInset: CGFloat = 13.0
|
||||
let rightInset: CGFloat = component.isNotification ? 15.0 : 13.0
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: avatarInset, y: avatarInset), size: avatarSize)
|
||||
if case let .peer(peer) = component.icon {
|
||||
if peer.smallProfileImage != nil {
|
||||
self.avatarNode.setPeerV2(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: peer,
|
||||
authorOfMessage: nil,
|
||||
overrideImage: nil,
|
||||
emptyColor: nil,
|
||||
clipStyle: .round,
|
||||
synchronousLoad: true,
|
||||
displayDimensions: avatarSize
|
||||
)
|
||||
} else {
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: peer,
|
||||
clipStyle: .round,
|
||||
synchronousLoad: true,
|
||||
displayDimensions: avatarSize
|
||||
)
|
||||
}
|
||||
}
|
||||
if self.avatarNode.bounds.isEmpty {
|
||||
self.avatarNode.frame = avatarFrame
|
||||
} else {
|
||||
transition.setFrame(view: self.avatarNode.view, frame: avatarFrame)
|
||||
}
|
||||
|
||||
var peerName = ""
|
||||
if !component.isNotification, case let .peer(peer) = component.icon {
|
||||
@@ -256,6 +250,8 @@ final class MessageItemComponent: Component {
|
||||
spacing = avatarSpacing
|
||||
case .icon:
|
||||
spacing = iconSpacing
|
||||
case .animation:
|
||||
spacing = iconSpacing
|
||||
}
|
||||
|
||||
let textSize = self.text.update(
|
||||
@@ -268,7 +264,8 @@ final class MessageItemComponent: Component {
|
||||
text: .plain(attributedText),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.0,
|
||||
spoilerColor: .white
|
||||
spoilerColor: .white,
|
||||
handleSpoilers: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - spacing - rightInset, height: .greatestFiniteMagnitude)
|
||||
@@ -276,7 +273,37 @@ final class MessageItemComponent: Component {
|
||||
|
||||
let size = CGSize(width: avatarInset + avatarSize.width + spacing + textSize.width + rightInset, height: max(minimalHeight, textSize.height + 15.0))
|
||||
|
||||
if case let .icon(iconName) = component.icon {
|
||||
switch component.icon {
|
||||
case let .peer(peer):
|
||||
if peer.smallProfileImage != nil {
|
||||
self.avatarNode.setPeerV2(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: peer,
|
||||
authorOfMessage: nil,
|
||||
overrideImage: nil,
|
||||
emptyColor: nil,
|
||||
clipStyle: .round,
|
||||
synchronousLoad: true,
|
||||
displayDimensions: avatarSize
|
||||
)
|
||||
} else {
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: peer,
|
||||
clipStyle: .round,
|
||||
synchronousLoad: true,
|
||||
displayDimensions: avatarSize
|
||||
)
|
||||
}
|
||||
if self.avatarNode.bounds.isEmpty {
|
||||
self.avatarNode.frame = avatarFrame
|
||||
} else {
|
||||
transition.setFrame(view: self.avatarNode.view, frame: avatarFrame)
|
||||
}
|
||||
self.avatarNode.isHidden = false
|
||||
case let .icon(iconName):
|
||||
let iconSize = self.icon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BundleIconComponent(name: iconName, tintColor: .white)),
|
||||
@@ -290,6 +317,31 @@ final class MessageItemComponent: Component {
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: iconFrame)
|
||||
}
|
||||
self.avatarNode.isHidden = true
|
||||
case let .animation(animationName):
|
||||
let iconSize = self.icon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(
|
||||
name: animationName
|
||||
),
|
||||
placeholderColor: nil,
|
||||
startingPosition: .end,
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
loop: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: avatarInset - 3.0, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
if let iconView = self.icon.view as? LottieComponent.View {
|
||||
if iconView.superview == nil {
|
||||
self.container.addSubview(iconView)
|
||||
iconView.playOnce()
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: iconFrame)
|
||||
}
|
||||
self.avatarNode.isHidden = true
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: avatarInset + avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
@@ -365,8 +417,6 @@ final class MessageItemComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,20 @@ final class MessageListComponent: Component {
|
||||
private let items: [Item]
|
||||
private let availableReactions: [ReactionItem]?
|
||||
private let sendActionTransition: SendActionTransition?
|
||||
private let openPeer: (EnginePeer) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
items: [Item],
|
||||
availableReactions: [ReactionItem]?,
|
||||
sendActionTransition: SendActionTransition?
|
||||
sendActionTransition: SendActionTransition?,
|
||||
openPeer: @escaping (EnginePeer) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.items = items
|
||||
self.availableReactions = availableReactions
|
||||
self.sendActionTransition = sendActionTransition
|
||||
self.openPeer = openPeer
|
||||
}
|
||||
|
||||
static func == (lhs: MessageListComponent, rhs: MessageListComponent) -> Bool {
|
||||
@@ -130,6 +133,15 @@ final class MessageListComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
for (_, itemView) in self.itemViews {
|
||||
if let view = itemView.view, view.point(inside: self.convert(point, to: view), with: event) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func update(component: MessageListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
@@ -167,7 +179,8 @@ final class MessageListComponent: Component {
|
||||
isNotification: item.isNotification,
|
||||
text: item.text,
|
||||
entities: item.entities,
|
||||
availableReactions: component.availableReactions
|
||||
availableReactions: component.availableReactions,
|
||||
openPeer: component.openPeer
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxWidth, height: .greatestFiniteMagnitude)
|
||||
|
||||
@@ -227,6 +227,8 @@ final class VideoChatParticipantAvatarComponent: Component {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
|
||||
@@ -693,8 +693,7 @@ final class VideoChatScreenComponent: Component {
|
||||
} else {
|
||||
text = title.isEmpty ? environment.strings.VoiceChat_EditTitleRemoveSuccess : environment.strings.VoiceChat_EditTitleSuccess(title).string
|
||||
}
|
||||
|
||||
self.presentUndoOverlay(content: .voiceChatFlag(text: text), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_vcflag"), text: text, duration: 3)
|
||||
})
|
||||
environment.controller()?.present(controller, in: .window(.root))
|
||||
})
|
||||
@@ -752,9 +751,7 @@ final class VideoChatScreenComponent: Component {
|
||||
switch result {
|
||||
case .linkCopied:
|
||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
return false
|
||||
}), in: .current)
|
||||
self.presentToast(icon: .icon("anim_linkcopied"), text: presentationData.strings.CallList_ToastCallLinkCopied_Text, duration: 3)
|
||||
case .openCall:
|
||||
break
|
||||
}
|
||||
@@ -841,8 +838,7 @@ final class VideoChatScreenComponent: Component {
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
|
||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: isSavedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
self.presentToast(icon: .icon(isSavedMessages ? "anim_savedmessages" : "anim_forward"), text: text, duration: 3)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
@@ -850,7 +846,7 @@ final class VideoChatScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
self.presentToast(icon: .icon("anim_linkcopied"), text: presentationData.strings.VoiceChat_InviteLinkCopiedText, duration: 3)
|
||||
}
|
||||
environment.controller()?.present(shareController, in: .window(.root))
|
||||
})
|
||||
@@ -893,8 +889,7 @@ final class VideoChatScreenComponent: Component {
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
|
||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: isSavedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
self.presentToast(icon: .icon(isSavedMessages ? "anim_savedmessages" : "anim_forward"), text: text, duration: 3)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
@@ -902,7 +897,7 @@ final class VideoChatScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
self.presentToast(icon: .icon("anim_linkcopied"), text: presentationData.strings.VoiceChat_InviteLinkCopiedText, duration: 3)
|
||||
}
|
||||
environment.controller()?.present(shareController, in: .window(.root))
|
||||
}
|
||||
@@ -1839,7 +1834,7 @@ final class VideoChatScreenComponent: Component {
|
||||
} else {
|
||||
text = environment.strings.VoiceChat_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||
}
|
||||
self.displayToast(icon: .peer(peer), text: text, duration: 3)
|
||||
self.presentToast(icon: .peer(peer), text: text, duration: 3)
|
||||
})
|
||||
|
||||
self.memberEventsDisposable?.dispose()
|
||||
@@ -1866,7 +1861,7 @@ final class VideoChatScreenComponent: Component {
|
||||
if displayEvent {
|
||||
let text = environment.strings.VoiceChat_PeerJoinedText("**\(event.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder))**").string
|
||||
|
||||
self.displayToast(icon: .peer(event.peer), text: text, duration: 3)
|
||||
self.presentToast(icon: .peer(event.peer), text: text, duration: 3)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -3936,6 +3931,8 @@ final class VideoChatScreenComponent: Component {
|
||||
icon = .peer(peer)
|
||||
case let .icon(name):
|
||||
icon = .icon(name)
|
||||
case let .animation(name):
|
||||
icon = .animation(name)
|
||||
}
|
||||
messageItems.insert(
|
||||
MessageListComponent.Item(
|
||||
@@ -3957,7 +3954,13 @@ final class VideoChatScreenComponent: Component {
|
||||
context: call.accountContext,
|
||||
items: messageItems,
|
||||
availableReactions: self.reactionItems,
|
||||
sendActionTransition: sendActionTransition
|
||||
sendActionTransition: sendActionTransition,
|
||||
openPeer: { [weak self] peer in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openPeer(peer)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: isTwoColumnLayout ? mainColumnWidth : min(440.0, availableSize.width - environment.safeInsets.left - environment.safeInsets.right), height: availableSize.height - messagesBottomInset)
|
||||
@@ -3975,7 +3978,6 @@ final class VideoChatScreenComponent: Component {
|
||||
let messagesListFrame = CGRect(origin: CGPoint(x: messageListOriginX, y: availableSize.height - messagesListSize.height - messagesBottomInset), size: messagesListSize)
|
||||
if let messagesListView = self.messagesList.view {
|
||||
if messagesListView.superview == nil {
|
||||
messagesListView.isUserInteractionEnabled = false
|
||||
self.containerView.addSubview(messagesListView)
|
||||
}
|
||||
transition.setFrame(view: messagesListView, frame: messagesListFrame)
|
||||
|
||||
@@ -136,7 +136,7 @@ extension VideoChatScreenComponent.View {
|
||||
} else {
|
||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||
}
|
||||
self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: EnginePeer(participant.peer), title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
self.presentToast(icon: .peer(EnginePeer(participant.peer)), text: text, duration: 3)
|
||||
}
|
||||
} else {
|
||||
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
|
||||
@@ -154,7 +154,7 @@ extension VideoChatScreenComponent.View {
|
||||
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
||||
return
|
||||
}
|
||||
self.presentUndoOverlay(content: .forward(savedMessages: false, text: environment.strings.UserInfo_LinkForwardTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true })
|
||||
self.presentToast(icon: .animation("anim_savedmessages"), text: environment.strings.UserInfo_LinkForwardTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string, duration: 3)
|
||||
})
|
||||
})]), in: .window(.root))
|
||||
} else {
|
||||
@@ -248,7 +248,7 @@ extension VideoChatScreenComponent.View {
|
||||
} else {
|
||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
self.presentToast(icon: .peer(peer), text: text, duration: 3)
|
||||
}
|
||||
}))
|
||||
} else if case let .legacyGroup(groupPeer) = groupPeer {
|
||||
@@ -320,7 +320,7 @@ extension VideoChatScreenComponent.View {
|
||||
} else {
|
||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
self.presentToast(icon: .peer(peer), text: text, duration: 3)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -361,7 +361,7 @@ extension VideoChatScreenComponent.View {
|
||||
if let link {
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
self.presentUndoOverlay(content: .linkCopied(title: nil, text: environment.strings.VoiceChat_InviteLinkCopiedText), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_linkcopied"), text: environment.strings.VoiceChat_InviteLinkCopiedText, duration: 3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ extension VideoChatScreenComponent.View {
|
||||
iconName = "Call/ToastMessagesDisabled"
|
||||
text = environment.strings.VoiceChat_ToastMessagesDisabled
|
||||
}
|
||||
self.displayToast(icon: .icon(iconName), text: text, duration: 3)
|
||||
self.presentToast(icon: .icon(iconName), text: text, duration: 3)
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ extension VideoChatScreenComponent.View {
|
||||
text = environment.strings.VoiceChat_RecordingStarted
|
||||
}
|
||||
|
||||
self.presentUndoOverlay(content: .voiceChatRecording(text: text), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_vcrecord"), text: text, duration: 3)
|
||||
groupCall.playTone(.recordingStarted)
|
||||
})
|
||||
environment.controller()?.present(controller, in: .window(.root))
|
||||
|
||||
@@ -133,7 +133,7 @@ extension VideoChatScreenComponent.View {
|
||||
}).start()
|
||||
}
|
||||
|
||||
self.presentUndoOverlay(content: .info(title: nil, text: environment.strings.VoiceChat_EditBioSuccess, timeout: nil, customUndoText: nil), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_infotip"), text: environment.strings.VoiceChat_EditBioSuccess, duration: 4)
|
||||
})
|
||||
environment.controller()?.present(controller, in: .window(.root))
|
||||
}
|
||||
@@ -155,7 +155,7 @@ extension VideoChatScreenComponent.View {
|
||||
}
|
||||
let _ = currentCall.accountContext.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).startStandalone()
|
||||
|
||||
self.presentUndoOverlay(content: .info(title: nil, text: environment.strings.VoiceChat_EditNameSuccess, timeout: nil, customUndoText: nil), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_infotip"), text: environment.strings.VoiceChat_EditNameSuccess, duration: 4)
|
||||
})
|
||||
environment.controller()?.present(controller, in: .window(.root))
|
||||
}
|
||||
@@ -190,7 +190,8 @@ extension VideoChatScreenComponent.View {
|
||||
f(.default)
|
||||
|
||||
if let participantPeer = participant.peer {
|
||||
self.presentUndoOverlay(content: .voiceChatCanSpeak(text: environment.strings.VoiceChat_UserCanNowSpeak(participantPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true })
|
||||
let text = environment.strings.VoiceChat_UserCanNowSpeak(participantPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||
self.presentToast(icon: .animation("anim_vcspeak"), text: text, duration: 3)
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
@@ -248,24 +249,11 @@ extension VideoChatScreenComponent.View {
|
||||
items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in
|
||||
return generateTintedImage(image: openIcon, color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
guard let self, let environment = self.environment, let currentCall = self.currentCall else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openPeer(peer)
|
||||
|
||||
guard let controller = environment.controller() as? VideoChatScreenV2Impl, let navigationController = controller.parentNavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = currentCall.accountContext
|
||||
controller.dismiss(completion: { [weak navigationController] in
|
||||
Queue.mainQueue().after(0.1) {
|
||||
guard let navigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
})
|
||||
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
|
||||
@@ -314,15 +302,14 @@ extension VideoChatScreenComponent.View {
|
||||
if groupCall.isConference {
|
||||
groupCall.kickPeer(id: peer.id)
|
||||
|
||||
//TODO:localize
|
||||
self.presentUndoOverlay(content: .banned(text: "You removed \(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)) from this call. They will no longer be able to join using the invite link."), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_banned"), text: environment.strings.VoiceChat_RemovedConferencePeerText(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string, duration: 3)
|
||||
} else {
|
||||
if let callPeerId = groupCall.peerId {
|
||||
let _ = groupCall.accountContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: groupCall.accountContext.engine, peerId: callPeerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)).start()
|
||||
groupCall.removedPeer(peer.id)
|
||||
}
|
||||
|
||||
self.presentUndoOverlay(content: .banned(text: environment.strings.VoiceChat_RemovedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string), action: { _ in return false })
|
||||
self.presentToast(icon: .animation("anim_banned"), text: environment.strings.VoiceChat_RemovedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string, duration: 3)
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -425,6 +412,24 @@ extension VideoChatScreenComponent.View {
|
||||
environment.controller()?.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func openPeer(_ peer: EnginePeer) {
|
||||
guard let environment = self.environment, let currentCall = self.currentCall else {
|
||||
return
|
||||
}
|
||||
guard let controller = environment.controller() as? VideoChatScreenV2Impl, let navigationController = controller.parentNavigationController else {
|
||||
return
|
||||
}
|
||||
let context = currentCall.accountContext
|
||||
controller.dismiss(completion: { [weak navigationController] in
|
||||
Queue.mainQueue().after(0.1) {
|
||||
guard let navigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func openAvatarForEditing(fromGallery: Bool = false, completion: @escaping () -> Void = {}) {
|
||||
guard let currentCall = self.currentCall else {
|
||||
return
|
||||
|
||||
@@ -161,7 +161,7 @@ final class VideoChatTitleComponent: Component {
|
||||
|
||||
let _ = activityStatusNode.transitionToState(.recordingVoice(NSAttributedString(string: value, font: Font.regular(13.0), textColor: UIColor(rgb: 0x34c759)), UIColor(rgb: 0x34c759)), animation: .none)
|
||||
let activityStatusSize = activityStatusNode.updateLayout(CGSize(width: currentSize.width, height: 100.0), alignment: .center)
|
||||
let activityStatusFrame = CGRect(origin: CGPoint(x: floor((currentSize.width - activityStatusSize.width) * 0.5), y: statusView.center.y - activityStatusSize.height * 0.5), size: activityStatusSize)
|
||||
let activityStatusFrame = CGRect(origin: CGPoint(x: floor((currentSize.width - activityStatusSize.width) * 0.5), y: statusView.center.y - activityStatusSize.height * 0.5 + 7.0), size: activityStatusSize)
|
||||
|
||||
let activityStatusNodeView = activityStatusNode.view
|
||||
activityStatusNodeView.center = activityStatusFrame.center
|
||||
|
||||
@@ -5,10 +5,11 @@ import TelegramCore
|
||||
enum VideoChatNotificationIcon {
|
||||
case peer(EnginePeer)
|
||||
case icon(String)
|
||||
case animation(String)
|
||||
}
|
||||
|
||||
extension VideoChatScreenComponent.View {
|
||||
func displayToast(icon: VideoChatNotificationIcon, text: String, duration: Int32) {
|
||||
func presentToast(icon: VideoChatNotificationIcon, text: String, duration: Int32) {
|
||||
let id = Int64.random(in: 0 ..< .max)
|
||||
|
||||
let expiresOn = Int32(CFAbsoluteTimeGetCurrent()) + duration
|
||||
|
||||
@@ -193,11 +193,12 @@ public final class ListSectionContentView: UIView {
|
||||
}
|
||||
}
|
||||
var separatorInset: CGFloat = 0.0
|
||||
let separatorRightInset: CGFloat = configuration.style == .glass ? 16.0 : 0.0
|
||||
if let itemComponentView = itemComponentView as? ListSectionComponentChildView {
|
||||
separatorInset = itemComponentView.separatorInset
|
||||
}
|
||||
|
||||
let itemSeparatorFrame = CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: width - separatorInset, height: UIScreenPixel))
|
||||
let itemSeparatorFrame = CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: width - separatorInset - separatorRightInset, height: UIScreenPixel))
|
||||
|
||||
if isAdded && itemComponentView is ListSubSectionComponent.View {
|
||||
readyItem.itemView.frame = itemFrame
|
||||
@@ -311,6 +312,14 @@ public final class ListSectionContentView: UIView {
|
||||
public final class ListSectionComponent: Component {
|
||||
public typealias ChildView = ListSectionComponentChildView
|
||||
|
||||
public final class TransitionHint {
|
||||
public let forceUpdate: Bool
|
||||
|
||||
public init(forceUpdate: Bool) {
|
||||
self.forceUpdate = forceUpdate
|
||||
}
|
||||
}
|
||||
|
||||
public enum Background: Equatable {
|
||||
case none(clipped: Bool)
|
||||
case all
|
||||
@@ -417,6 +426,11 @@ public final class ListSectionComponent: Component {
|
||||
func update(component: ListSectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
var forceUpdate = false
|
||||
if let hint = transition.userData(TransitionHint.self) {
|
||||
forceUpdate = hint.forceUpdate
|
||||
}
|
||||
|
||||
let headerSideInset: CGFloat = 16.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
@@ -472,6 +486,7 @@ public final class ListSectionComponent: Component {
|
||||
transition: itemTransition,
|
||||
component: item.component,
|
||||
environment: {},
|
||||
forceUpdate: forceUpdate,
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||
)
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ final class GiftListItemComponent: Component {
|
||||
let starGifts: [StarGift]
|
||||
let selectedId: Int64?
|
||||
let selectionUpdated: (StarGift.UniqueGift) -> Void
|
||||
let onTabChange: () -> Void
|
||||
let tag: AnyObject?
|
||||
let updated: (ComponentTransition) -> Void
|
||||
|
||||
@@ -41,6 +42,7 @@ final class GiftListItemComponent: Component {
|
||||
starGifts: [StarGift],
|
||||
selectedId: Int64?,
|
||||
selectionUpdated: @escaping (StarGift.UniqueGift) -> Void,
|
||||
onTabChange: @escaping () -> Void,
|
||||
tag: AnyObject?,
|
||||
updated: @escaping (ComponentTransition) -> Void
|
||||
) {
|
||||
@@ -52,6 +54,7 @@ final class GiftListItemComponent: Component {
|
||||
self.starGifts = starGifts
|
||||
self.selectedId = selectedId
|
||||
self.selectionUpdated = selectionUpdated
|
||||
self.onTabChange = onTabChange
|
||||
self.tag = tag
|
||||
self.updated = updated
|
||||
}
|
||||
@@ -150,12 +153,17 @@ final class GiftListItemComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let previousGiftId = self.selectedGiftId
|
||||
self.selectedGiftId = id
|
||||
|
||||
if id == 0 {
|
||||
self.resaleGiftsState = nil
|
||||
self.resaleGiftsDisposable.set(nil)
|
||||
} else {
|
||||
if previousGiftId == 0 {
|
||||
component.onTabChange()
|
||||
}
|
||||
|
||||
let resaleGiftsContext: ResaleGiftsContext
|
||||
if let current = self.resaleGiftsContexts[id] {
|
||||
resaleGiftsContext = current
|
||||
@@ -173,7 +181,6 @@ final class GiftListItemComponent: Component {
|
||||
self.resaleGiftsState = state
|
||||
if !self.isUpdating {
|
||||
let transition: ComponentTransition = isFirstTime ? .easeInOut(duration: 0.25) : .immediate
|
||||
self.state?.updated(transition: transition)
|
||||
component.updated(transition)
|
||||
}
|
||||
isFirstTime = false
|
||||
@@ -182,7 +189,6 @@ final class GiftListItemComponent: Component {
|
||||
|
||||
if !self.isUpdating {
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||
self.state?.updated(transition: transition)
|
||||
component.updated(transition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,18 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.maskNode)
|
||||
}
|
||||
|
||||
var hasBackground = false
|
||||
let subject: PeerInfoCoverComponent.Subject?
|
||||
if let status = item.peer?.emojiStatus, case .starGift = status.content {
|
||||
subject = .status(status)
|
||||
hasBackground = true
|
||||
} else if let peer = item.peer {
|
||||
subject = .peer(peer)
|
||||
hasBackground = peer.profileColor != nil
|
||||
} else {
|
||||
subject = nil
|
||||
}
|
||||
|
||||
if params.isStandalone {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: item.showBackground ? 1.0 : 0.0)
|
||||
@@ -195,7 +207,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
|
||||
self.backgroundNode.isHidden = false
|
||||
self.topStripeNode.isHidden = true
|
||||
self.bottomStripeNode.isHidden = false
|
||||
self.bottomStripeNode.isHidden = hasBackground
|
||||
self.maskNode.isHidden = true
|
||||
|
||||
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
@@ -225,7 +237,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
hasBottomCorners = true
|
||||
self.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
|
||||
self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
self.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
@@ -239,14 +251,6 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
let avatarSize: CGFloat = 104.0
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floor((coverFrame.width - avatarSize) * 0.5), y: coverFrame.minY + item.topInset + 24.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
|
||||
let subject: PeerInfoCoverComponent.Subject?
|
||||
if let status = item.peer?.emojiStatus, case .starGift = status.content {
|
||||
subject = .status(status)
|
||||
} else if let peer = item.peer {
|
||||
subject = .peer(peer)
|
||||
} else {
|
||||
subject = nil
|
||||
}
|
||||
let _ = self.background.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PeerInfoCoverComponent(
|
||||
@@ -314,7 +318,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
credibilityIcon = .emojiStatus(emojiStatus)
|
||||
} else if peer.isVerified {
|
||||
credibilityIcon = .verified
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != item.context.account.peerId) {
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
credibilityIcon = .premium
|
||||
} else {
|
||||
credibilityIcon = .none
|
||||
|
||||
@@ -46,9 +46,11 @@ final class UserAppearanceScreenComponent: Component {
|
||||
|
||||
public final class TransitionHint {
|
||||
public let animateTabChange: Bool
|
||||
public let forceGiftsUpdate: Bool
|
||||
|
||||
public init(animateTabChange: Bool) {
|
||||
public init(animateTabChange: Bool = false, forceGiftsUpdate: Bool = false) {
|
||||
self.animateTabChange = animateTabChange
|
||||
self.forceGiftsUpdate = forceGiftsUpdate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +175,8 @@ final class UserAppearanceScreenComponent: Component {
|
||||
}
|
||||
private var currentSection: Section = .profile
|
||||
|
||||
private let previewShadowView = UIImageView(image: generatePreviewShadowImage())
|
||||
|
||||
private let profilePreview = ComponentView<Empty>()
|
||||
private let profileColorSection = ComponentView<Empty>()
|
||||
private let profileResetColorSection = ComponentView<Empty>()
|
||||
@@ -183,7 +187,6 @@ final class UserAppearanceScreenComponent: Component {
|
||||
private let nameGiftsSection = ComponentView<Empty>()
|
||||
|
||||
private var isUpdating: Bool = false
|
||||
private var forceNextUpdate = false
|
||||
|
||||
private var component: UserAppearanceScreenComponent?
|
||||
private(set) weak var state: EmptyComponentState?
|
||||
@@ -246,6 +249,7 @@ final class UserAppearanceScreenComponent: Component {
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
self.edgeEffectView.isUserInteractionEnabled = false
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
@@ -256,6 +260,8 @@ final class UserAppearanceScreenComponent: Component {
|
||||
|
||||
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
||||
|
||||
self.containerView.addSubview(self.previewShadowView)
|
||||
|
||||
self.addSubview(self.edgeEffectView)
|
||||
|
||||
self.backButton.action = { [weak self] _, _ in
|
||||
@@ -946,13 +952,7 @@ final class UserAppearanceScreenComponent: Component {
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
var forceUpdate = false
|
||||
if self.forceNextUpdate {
|
||||
self.forceNextUpdate = false
|
||||
forceUpdate = true
|
||||
}
|
||||
|
||||
|
||||
let environment = environment[EnvironmentType.self].value
|
||||
let themeUpdated = self.environment?.theme !== environment.theme
|
||||
self.environment = environment
|
||||
@@ -963,8 +963,10 @@ final class UserAppearanceScreenComponent: Component {
|
||||
let theme = environment.theme
|
||||
|
||||
var animateTabChange = false
|
||||
var forceGiftsUpdate = false
|
||||
if let hint = transition.userData(TransitionHint.self) {
|
||||
animateTabChange = hint.animateTabChange
|
||||
forceGiftsUpdate = hint.forceGiftsUpdate
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
@@ -1130,11 +1132,11 @@ final class UserAppearanceScreenComponent: Component {
|
||||
self.selectedProfileGift = nil
|
||||
self.updatedPeerProfileColor = nil
|
||||
self.updatedPeerProfileEmoji = nil
|
||||
self.updatedPeerStatus = nil
|
||||
}
|
||||
} else {
|
||||
self.currentSection = updatedSection
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||
}
|
||||
self.currentSection = updatedSection
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1162,6 +1164,8 @@ final class UserAppearanceScreenComponent: Component {
|
||||
|
||||
let itemCornerRadius: CGFloat = 26.0
|
||||
|
||||
transition.setTintColor(view: self.previewShadowView, color: environment.theme.list.itemBlocksBackgroundColor)
|
||||
|
||||
switch self.currentSection {
|
||||
case .profile:
|
||||
var transition = transition
|
||||
@@ -1209,7 +1213,10 @@ final class UserAppearanceScreenComponent: Component {
|
||||
}
|
||||
transition.setFrame(view: profilePreviewView, frame: profilePreviewFrame)
|
||||
}
|
||||
contentHeight += profilePreviewSize.height - 18.0
|
||||
contentHeight += profilePreviewSize.height - 38.0
|
||||
|
||||
transition.setFrame(view: self.previewShadowView, frame: profilePreviewFrame.insetBy(dx: -30.0, dy: -30.0))
|
||||
previewTransition.setAlpha(view: self.previewShadowView, alpha: !self.scrolledUp ? 1.0 : 0.0)
|
||||
|
||||
var profileLogoContents: [AnyComponentWithIdentity<Empty>] = []
|
||||
profileLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
@@ -1257,11 +1264,13 @@ final class UserAppearanceScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
if self.selectedProfileGift != nil {
|
||||
|
||||
} else {
|
||||
self.currentSection = .name
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||
self.selectedProfileGift = nil
|
||||
self.updatedPeerProfileColor = nil
|
||||
self.updatedPeerProfileEmoji = nil
|
||||
self.updatedPeerStatus = nil
|
||||
}
|
||||
self.currentSection = .name
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||
}
|
||||
)),
|
||||
items: [
|
||||
@@ -1391,8 +1400,10 @@ final class UserAppearanceScreenComponent: Component {
|
||||
if let status = resolvedState.emojiStatus, case let .starGift(id, _, _, _, _, _, _, _, _) = status.content {
|
||||
selectedGiftId = id
|
||||
}
|
||||
|
||||
let listTransition = transition.withUserData(ListSectionComponent.TransitionHint(forceUpdate: forceGiftsUpdate))
|
||||
let giftsSectionSize = self.profileGiftsSection.update(
|
||||
transition: transition,
|
||||
transition: listTransition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
style: .glass,
|
||||
@@ -1454,13 +1465,18 @@ final class UserAppearanceScreenComponent: Component {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
},
|
||||
onTabChange: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let sectionView = self.profileGiftsSection.view {
|
||||
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: sectionView.frame.minY - 240.0), animated: true)
|
||||
}
|
||||
},
|
||||
tag: giftListTag,
|
||||
updated: { [weak self] transition in
|
||||
if let self {
|
||||
self.forceNextUpdate = true
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: transition)
|
||||
}
|
||||
if let self, !self.isUpdating {
|
||||
self.state?.updated(transition: transition.withUserData(TransitionHint(forceGiftsUpdate: true)))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1469,8 +1485,8 @@ final class UserAppearanceScreenComponent: Component {
|
||||
displaySeparators: false
|
||||
)),
|
||||
environment: {},
|
||||
forceUpdate: forceUpdate,
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
forceUpdate: forceGiftsUpdate,
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
||||
)
|
||||
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
||||
if let giftsSectionView = self.profileGiftsSection.view {
|
||||
@@ -1569,7 +1585,10 @@ final class UserAppearanceScreenComponent: Component {
|
||||
}
|
||||
transition.setFrame(view: namePreviewView, frame: namePreviewFrame)
|
||||
}
|
||||
contentHeight += namePreviewSize.height - 18.0
|
||||
contentHeight += namePreviewSize.height - 38.0
|
||||
|
||||
transition.setFrame(view: self.previewShadowView, frame: namePreviewFrame.insetBy(dx: -30.0, dy: -30.0))
|
||||
previewTransition.setAlpha(view: self.previewShadowView, alpha: !self.scrolledUp ? 1.0 : 0.0)
|
||||
|
||||
let nameColorSectionSize = self.nameColorSection.update(
|
||||
transition: transition,
|
||||
@@ -1655,8 +1674,9 @@ final class UserAppearanceScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
let listTransition = transition.withUserData(ListSectionComponent.TransitionHint(forceUpdate: forceGiftsUpdate))
|
||||
let giftsSectionSize = self.nameGiftsSection.update(
|
||||
transition: transition,
|
||||
transition: listTransition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
style: .glass,
|
||||
@@ -1692,12 +1712,18 @@ final class UserAppearanceScreenComponent: Component {
|
||||
self.updatedPeerNameEmoji = peerColor.backgroundEmojiId
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
},
|
||||
onTabChange: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let sectionView = self.nameGiftsSection.view {
|
||||
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: sectionView.frame.minY - 240.0), animated: true)
|
||||
}
|
||||
},
|
||||
tag: giftListTag,
|
||||
updated: { [weak self] transition in
|
||||
if let self {
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: transition)
|
||||
}
|
||||
if let self, !self.isUpdating {
|
||||
self.state?.updated(transition: transition.withUserData(TransitionHint(forceGiftsUpdate: true)))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1706,8 +1732,8 @@ final class UserAppearanceScreenComponent: Component {
|
||||
displaySeparators: false
|
||||
)),
|
||||
environment: {},
|
||||
forceUpdate: forceUpdate,
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
forceUpdate: forceGiftsUpdate,
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
||||
)
|
||||
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
||||
if let giftsSectionView = self.nameGiftsSection.view {
|
||||
@@ -1830,14 +1856,14 @@ final class UserAppearanceScreenComponent: Component {
|
||||
transition.setAlpha(view: buttonView, alpha: 1.0)
|
||||
}
|
||||
|
||||
let edgeEffectHeight: CGFloat = availableSize.height - buttonY + 24.0
|
||||
let edgeEffectHeight: CGFloat = availableSize.height - buttonY + 36.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
|
||||
let previousBounds = self.scrollView.bounds
|
||||
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||
let scrollViewFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 30.0), size: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight - 30.0))
|
||||
let scrollViewFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 50.0), size: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight - 50.0))
|
||||
if self.scrollView.frame != scrollViewFrame {
|
||||
self.scrollView.frame = scrollViewFrame
|
||||
}
|
||||
@@ -1856,7 +1882,7 @@ final class UserAppearanceScreenComponent: Component {
|
||||
}
|
||||
|
||||
self.topOverscrollLayer.backgroundColor = environment.theme.list.itemBlocksBackgroundColor.cgColor
|
||||
self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: sideInset, y: -1000.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: 1315.0))
|
||||
self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: sideInset, y: -1000.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: 1340.0))
|
||||
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
@@ -2044,3 +2070,32 @@ final class TopBottomCornersComponent: Component {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private func generatePreviewShadowImage() -> UIImage {
|
||||
let cornerRadius: CGFloat = 26.0
|
||||
let shadowInset: CGFloat = 30.0
|
||||
|
||||
let side = (cornerRadius + 5.0) * 2.0
|
||||
let fullSide = shadowInset * 2.0 + side
|
||||
|
||||
return generateImage(CGSize(width: fullSide, height: fullSide), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let edgeHeight = shadowInset + cornerRadius + 11.0
|
||||
context.clip(to: CGRect(x: shadowInset, y: size.height - edgeHeight, width: side, height: edgeHeight))
|
||||
|
||||
let rect = CGRect(origin: .zero, size: CGSize(width: fullSide, height: fullSide)).insetBy(dx: shadowInset + 1.0, dy: shadowInset + 2.0)
|
||||
let path = CGPath(roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
|
||||
|
||||
let drawShadow = {
|
||||
context.addPath(path)
|
||||
context.setShadow(offset: CGSize(), blur: 65.0, color: UIColor.black.cgColor)
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
context.fillPath()
|
||||
}
|
||||
|
||||
drawShadow()
|
||||
drawShadow()
|
||||
drawShadow()
|
||||
})!.stretchableImage(withLeftCapWidth: Int(shadowInset + cornerRadius + 5), topCapHeight: Int(shadowInset + cornerRadius + 5)).withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user