mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +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.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.NoNameGiftsPlaceholder" = "You don't have any gifts you can use as styles for your name.";
|
||||||
"ProfileColorSetup.BrowseGiftsForPurchase" = "Browse gifts available for purchase >";
|
"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")
|
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) {
|
public func updateVisibility(_ isVisible: Bool) {
|
||||||
self.textNode.visibility = isVisible
|
self.textNode.visibility = isVisible
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import TextFormat
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ReactionSelectionNode
|
import ReactionSelectionNode
|
||||||
import BundleIconComponent
|
import BundleIconComponent
|
||||||
|
import LottieComponent
|
||||||
import Markdown
|
import Markdown
|
||||||
|
|
||||||
private let glassColor = UIColor(rgb: 0x25272e, alpha: 0.72)
|
private let glassColor = UIColor(rgb: 0x25272e, alpha: 0.72)
|
||||||
@@ -22,6 +23,7 @@ final class MessageItemComponent: Component {
|
|||||||
public enum Icon: Equatable {
|
public enum Icon: Equatable {
|
||||||
case peer(EnginePeer)
|
case peer(EnginePeer)
|
||||||
case icon(String)
|
case icon(String)
|
||||||
|
case animation(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@@ -30,7 +32,7 @@ final class MessageItemComponent: Component {
|
|||||||
private let text: String
|
private let text: String
|
||||||
private let entities: [MessageTextEntity]
|
private let entities: [MessageTextEntity]
|
||||||
private let availableReactions: [ReactionItem]?
|
private let availableReactions: [ReactionItem]?
|
||||||
private let avatarTapped: () -> Void
|
private let openPeer: ((EnginePeer) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@@ -39,7 +41,7 @@ final class MessageItemComponent: Component {
|
|||||||
text: String,
|
text: String,
|
||||||
entities: [MessageTextEntity],
|
entities: [MessageTextEntity],
|
||||||
availableReactions: [ReactionItem]?,
|
availableReactions: [ReactionItem]?,
|
||||||
avatarTapped: @escaping () -> Void = {}
|
openPeer: ((EnginePeer) -> Void)?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
@@ -47,7 +49,7 @@ final class MessageItemComponent: Component {
|
|||||||
self.text = text
|
self.text = text
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
self.availableReactions = availableReactions
|
self.availableReactions = availableReactions
|
||||||
self.avatarTapped = avatarTapped
|
self.openPeer = openPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
static func == (lhs: MessageItemComponent, rhs: MessageItemComponent) -> Bool {
|
static func == (lhs: MessageItemComponent, rhs: MessageItemComponent) -> Bool {
|
||||||
@@ -77,6 +79,9 @@ final class MessageItemComponent: Component {
|
|||||||
private let text: ComponentView<Empty>
|
private let text: ComponentView<Empty>
|
||||||
weak var standaloneReactionAnimation: StandaloneReactionAnimation?
|
weak var standaloneReactionAnimation: StandaloneReactionAnimation?
|
||||||
|
|
||||||
|
private var cachedEntities: [MessageTextEntity]?
|
||||||
|
private var entityFiles: [MediaId: TelegramMediaFile] = [:]
|
||||||
|
|
||||||
private var component: MessageItemComponent?
|
private var component: MessageItemComponent?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
@@ -95,12 +100,21 @@ final class MessageItemComponent: Component {
|
|||||||
self.addSubview(self.container)
|
self.addSubview(self.container)
|
||||||
self.container.addSubview(self.background)
|
self.container.addSubview(self.background)
|
||||||
self.container.addSubview(self.avatarNode.view)
|
self.container.addSubview(self.avatarNode.view)
|
||||||
|
|
||||||
|
self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.avatarTapped)))
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
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) {
|
func animateFrom(globalFrame: CGRect, cornerRadius: CGFloat, textSnapshotView: UIView, transition: ComponentTransition) {
|
||||||
guard let superview = self.superview?.superview?.superview else {
|
guard let superview = self.superview?.superview?.superview else {
|
||||||
return
|
return
|
||||||
@@ -143,8 +157,17 @@ final class MessageItemComponent: Component {
|
|||||||
transition.animateScale(view: self.avatarNode.view, from: 0.01, to: 1.0)
|
transition.animateScale(view: self.avatarNode.view, from: 0.01, to: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cachedEntities: [MessageTextEntity]?
|
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||||
private var entityFiles: [MediaId: TelegramMediaFile] = [:]
|
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 {
|
func update(component: MessageItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
let isFirstTime = self.component == nil
|
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 avatarSize = CGSize(width: component.isNotification ? 30.0 : 28.0, height: component.isNotification ? 30.0 : 28.0)
|
||||||
let avatarSpacing: CGFloat = 10.0
|
let avatarSpacing: CGFloat = 10.0
|
||||||
let iconSpacing: 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)
|
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 = ""
|
var peerName = ""
|
||||||
if !component.isNotification, case let .peer(peer) = component.icon {
|
if !component.isNotification, case let .peer(peer) = component.icon {
|
||||||
@@ -256,6 +250,8 @@ final class MessageItemComponent: Component {
|
|||||||
spacing = avatarSpacing
|
spacing = avatarSpacing
|
||||||
case .icon:
|
case .icon:
|
||||||
spacing = iconSpacing
|
spacing = iconSpacing
|
||||||
|
case .animation:
|
||||||
|
spacing = iconSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
let textSize = self.text.update(
|
let textSize = self.text.update(
|
||||||
@@ -268,7 +264,8 @@ final class MessageItemComponent: Component {
|
|||||||
text: .plain(attributedText),
|
text: .plain(attributedText),
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.0,
|
lineSpacing: 0.0,
|
||||||
spoilerColor: .white
|
spoilerColor: .white,
|
||||||
|
handleSpoilers: true
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - spacing - rightInset, height: .greatestFiniteMagnitude)
|
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))
|
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(
|
let iconSize = self.icon.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(BundleIconComponent(name: iconName, tintColor: .white)),
|
component: AnyComponent(BundleIconComponent(name: iconName, tintColor: .white)),
|
||||||
@@ -290,6 +317,31 @@ final class MessageItemComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setFrame(view: iconView, frame: iconFrame)
|
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)
|
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
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,17 +34,20 @@ final class MessageListComponent: Component {
|
|||||||
private let items: [Item]
|
private let items: [Item]
|
||||||
private let availableReactions: [ReactionItem]?
|
private let availableReactions: [ReactionItem]?
|
||||||
private let sendActionTransition: SendActionTransition?
|
private let sendActionTransition: SendActionTransition?
|
||||||
|
private let openPeer: (EnginePeer) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
items: [Item],
|
items: [Item],
|
||||||
availableReactions: [ReactionItem]?,
|
availableReactions: [ReactionItem]?,
|
||||||
sendActionTransition: SendActionTransition?
|
sendActionTransition: SendActionTransition?,
|
||||||
|
openPeer: @escaping (EnginePeer) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.items = items
|
self.items = items
|
||||||
self.availableReactions = availableReactions
|
self.availableReactions = availableReactions
|
||||||
self.sendActionTransition = sendActionTransition
|
self.sendActionTransition = sendActionTransition
|
||||||
|
self.openPeer = openPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
static func == (lhs: MessageListComponent, rhs: MessageListComponent) -> Bool {
|
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 {
|
func update(component: MessageListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
@@ -167,7 +179,8 @@ final class MessageListComponent: Component {
|
|||||||
isNotification: item.isNotification,
|
isNotification: item.isNotification,
|
||||||
text: item.text,
|
text: item.text,
|
||||||
entities: item.entities,
|
entities: item.entities,
|
||||||
availableReactions: component.availableReactions
|
availableReactions: component.availableReactions,
|
||||||
|
openPeer: component.openPeer
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: maxWidth, height: .greatestFiniteMagnitude)
|
containerSize: CGSize(width: maxWidth, height: .greatestFiniteMagnitude)
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ final class VideoChatParticipantAvatarComponent: Component {
|
|||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isUserInteractionEnabled = false
|
||||||
|
|
||||||
let previousComponent = self.component
|
let previousComponent = self.component
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
|
|||||||
@@ -693,8 +693,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
text = title.isEmpty ? environment.strings.VoiceChat_EditTitleRemoveSuccess : environment.strings.VoiceChat_EditTitleSuccess(title).string
|
text = title.isEmpty ? environment.strings.VoiceChat_EditTitleRemoveSuccess : environment.strings.VoiceChat_EditTitleSuccess(title).string
|
||||||
}
|
}
|
||||||
|
self.presentToast(icon: .animation("anim_vcflag"), text: text, duration: 3)
|
||||||
self.presentUndoOverlay(content: .voiceChatFlag(text: text), action: { _ in return false })
|
|
||||||
})
|
})
|
||||||
environment.controller()?.present(controller, in: .window(.root))
|
environment.controller()?.present(controller, in: .window(.root))
|
||||||
})
|
})
|
||||||
@@ -752,9 +751,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
switch result {
|
switch result {
|
||||||
case .linkCopied:
|
case .linkCopied:
|
||||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
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
|
self.presentToast(icon: .icon("anim_linkcopied"), text: presentationData.strings.CallList_ToastCallLinkCopied_Text, duration: 3)
|
||||||
return false
|
|
||||||
}), in: .current)
|
|
||||||
case .openCall:
|
case .openCall:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -841,8 +838,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
|
self.presentToast(icon: .icon(isSavedMessages ? "anim_savedmessages" : "anim_forward"), text: text, duration: 3)
|
||||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: isSavedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
shareController.actionCompleted = { [weak self] in
|
shareController.actionCompleted = { [weak self] in
|
||||||
@@ -850,7 +846,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
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))
|
environment.controller()?.present(shareController, in: .window(.root))
|
||||||
})
|
})
|
||||||
@@ -893,8 +889,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
|
self.presentToast(icon: .icon(isSavedMessages ? "anim_savedmessages" : "anim_forward"), text: text, duration: 3)
|
||||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: isSavedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
shareController.actionCompleted = { [weak self] in
|
shareController.actionCompleted = { [weak self] in
|
||||||
@@ -902,7 +897,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
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))
|
environment.controller()?.present(shareController, in: .window(.root))
|
||||||
}
|
}
|
||||||
@@ -1839,7 +1834,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
text = environment.strings.VoiceChat_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
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()
|
self.memberEventsDisposable?.dispose()
|
||||||
@@ -1866,7 +1861,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
if displayEvent {
|
if displayEvent {
|
||||||
let text = environment.strings.VoiceChat_PeerJoinedText("**\(event.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder))**").string
|
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 {
|
} else {
|
||||||
@@ -3936,6 +3931,8 @@ final class VideoChatScreenComponent: Component {
|
|||||||
icon = .peer(peer)
|
icon = .peer(peer)
|
||||||
case let .icon(name):
|
case let .icon(name):
|
||||||
icon = .icon(name)
|
icon = .icon(name)
|
||||||
|
case let .animation(name):
|
||||||
|
icon = .animation(name)
|
||||||
}
|
}
|
||||||
messageItems.insert(
|
messageItems.insert(
|
||||||
MessageListComponent.Item(
|
MessageListComponent.Item(
|
||||||
@@ -3957,7 +3954,13 @@ final class VideoChatScreenComponent: Component {
|
|||||||
context: call.accountContext,
|
context: call.accountContext,
|
||||||
items: messageItems,
|
items: messageItems,
|
||||||
availableReactions: self.reactionItems,
|
availableReactions: self.reactionItems,
|
||||||
sendActionTransition: sendActionTransition
|
sendActionTransition: sendActionTransition,
|
||||||
|
openPeer: { [weak self] peer in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.openPeer(peer)
|
||||||
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: isTwoColumnLayout ? mainColumnWidth : min(440.0, availableSize.width - environment.safeInsets.left - environment.safeInsets.right), height: availableSize.height - messagesBottomInset)
|
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)
|
let messagesListFrame = CGRect(origin: CGPoint(x: messageListOriginX, y: availableSize.height - messagesListSize.height - messagesBottomInset), size: messagesListSize)
|
||||||
if let messagesListView = self.messagesList.view {
|
if let messagesListView = self.messagesList.view {
|
||||||
if messagesListView.superview == nil {
|
if messagesListView.superview == nil {
|
||||||
messagesListView.isUserInteractionEnabled = false
|
|
||||||
self.containerView.addSubview(messagesListView)
|
self.containerView.addSubview(messagesListView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: messagesListView, frame: messagesListFrame)
|
transition.setFrame(view: messagesListView, frame: messagesListFrame)
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ extension VideoChatScreenComponent.View {
|
|||||||
} else {
|
} else {
|
||||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
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 {
|
} else {
|
||||||
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
|
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 {
|
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
||||||
return
|
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))
|
})]), in: .window(.root))
|
||||||
} else {
|
} else {
|
||||||
@@ -248,7 +248,7 @@ extension VideoChatScreenComponent.View {
|
|||||||
} else {
|
} else {
|
||||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
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 {
|
} else if case let .legacyGroup(groupPeer) = groupPeer {
|
||||||
@@ -320,7 +320,7 @@ extension VideoChatScreenComponent.View {
|
|||||||
} else {
|
} else {
|
||||||
text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
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 {
|
if let link {
|
||||||
UIPasteboard.general.string = 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"
|
iconName = "Call/ToastMessagesDisabled"
|
||||||
text = environment.strings.VoiceChat_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
|
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)
|
groupCall.playTone(.recordingStarted)
|
||||||
})
|
})
|
||||||
environment.controller()?.present(controller, in: .window(.root))
|
environment.controller()?.present(controller, in: .window(.root))
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ extension VideoChatScreenComponent.View {
|
|||||||
}).start()
|
}).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))
|
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()
|
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))
|
environment.controller()?.present(controller, in: .window(.root))
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,8 @@ extension VideoChatScreenComponent.View {
|
|||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
if let participantPeer = participant.peer {
|
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 {
|
} else {
|
||||||
@@ -248,23 +249,10 @@ extension VideoChatScreenComponent.View {
|
|||||||
items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in
|
||||||
return generateTintedImage(image: openIcon, color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: openIcon, color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
guard let self, let environment = self.environment, let currentCall = self.currentCall else {
|
guard let self else {
|
||||||
return
|
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)
|
f(.dismissWithoutContent)
|
||||||
})))
|
})))
|
||||||
@@ -314,15 +302,14 @@ extension VideoChatScreenComponent.View {
|
|||||||
if groupCall.isConference {
|
if groupCall.isConference {
|
||||||
groupCall.kickPeer(id: peer.id)
|
groupCall.kickPeer(id: peer.id)
|
||||||
|
|
||||||
//TODO:localize
|
self.presentToast(icon: .animation("anim_banned"), text: environment.strings.VoiceChat_RemovedConferencePeerText(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string, duration: 3)
|
||||||
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 })
|
|
||||||
} else {
|
} else {
|
||||||
if let callPeerId = groupCall.peerId {
|
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()
|
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)
|
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)
|
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 = {}) {
|
private func openAvatarForEditing(fromGallery: Bool = false, completion: @escaping () -> Void = {}) {
|
||||||
guard let currentCall = self.currentCall else {
|
guard let currentCall = self.currentCall else {
|
||||||
return
|
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 _ = 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 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
|
let activityStatusNodeView = activityStatusNode.view
|
||||||
activityStatusNodeView.center = activityStatusFrame.center
|
activityStatusNodeView.center = activityStatusFrame.center
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import TelegramCore
|
|||||||
enum VideoChatNotificationIcon {
|
enum VideoChatNotificationIcon {
|
||||||
case peer(EnginePeer)
|
case peer(EnginePeer)
|
||||||
case icon(String)
|
case icon(String)
|
||||||
|
case animation(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoChatScreenComponent.View {
|
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 id = Int64.random(in: 0 ..< .max)
|
||||||
|
|
||||||
let expiresOn = Int32(CFAbsoluteTimeGetCurrent()) + duration
|
let expiresOn = Int32(CFAbsoluteTimeGetCurrent()) + duration
|
||||||
|
|||||||
@@ -193,11 +193,12 @@ public final class ListSectionContentView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var separatorInset: CGFloat = 0.0
|
var separatorInset: CGFloat = 0.0
|
||||||
|
let separatorRightInset: CGFloat = configuration.style == .glass ? 16.0 : 0.0
|
||||||
if let itemComponentView = itemComponentView as? ListSectionComponentChildView {
|
if let itemComponentView = itemComponentView as? ListSectionComponentChildView {
|
||||||
separatorInset = itemComponentView.separatorInset
|
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 {
|
if isAdded && itemComponentView is ListSubSectionComponent.View {
|
||||||
readyItem.itemView.frame = itemFrame
|
readyItem.itemView.frame = itemFrame
|
||||||
@@ -311,6 +312,14 @@ public final class ListSectionContentView: UIView {
|
|||||||
public final class ListSectionComponent: Component {
|
public final class ListSectionComponent: Component {
|
||||||
public typealias ChildView = ListSectionComponentChildView
|
public typealias ChildView = ListSectionComponentChildView
|
||||||
|
|
||||||
|
public final class TransitionHint {
|
||||||
|
public let forceUpdate: Bool
|
||||||
|
|
||||||
|
public init(forceUpdate: Bool) {
|
||||||
|
self.forceUpdate = forceUpdate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum Background: Equatable {
|
public enum Background: Equatable {
|
||||||
case none(clipped: Bool)
|
case none(clipped: Bool)
|
||||||
case all
|
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 {
|
func update(component: ListSectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
|
var forceUpdate = false
|
||||||
|
if let hint = transition.userData(TransitionHint.self) {
|
||||||
|
forceUpdate = hint.forceUpdate
|
||||||
|
}
|
||||||
|
|
||||||
let headerSideInset: CGFloat = 16.0
|
let headerSideInset: CGFloat = 16.0
|
||||||
|
|
||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
@@ -472,6 +486,7 @@ public final class ListSectionComponent: Component {
|
|||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
component: item.component,
|
component: item.component,
|
||||||
environment: {},
|
environment: {},
|
||||||
|
forceUpdate: forceUpdate,
|
||||||
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ final class GiftListItemComponent: Component {
|
|||||||
let starGifts: [StarGift]
|
let starGifts: [StarGift]
|
||||||
let selectedId: Int64?
|
let selectedId: Int64?
|
||||||
let selectionUpdated: (StarGift.UniqueGift) -> Void
|
let selectionUpdated: (StarGift.UniqueGift) -> Void
|
||||||
|
let onTabChange: () -> Void
|
||||||
let tag: AnyObject?
|
let tag: AnyObject?
|
||||||
let updated: (ComponentTransition) -> Void
|
let updated: (ComponentTransition) -> Void
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ final class GiftListItemComponent: Component {
|
|||||||
starGifts: [StarGift],
|
starGifts: [StarGift],
|
||||||
selectedId: Int64?,
|
selectedId: Int64?,
|
||||||
selectionUpdated: @escaping (StarGift.UniqueGift) -> Void,
|
selectionUpdated: @escaping (StarGift.UniqueGift) -> Void,
|
||||||
|
onTabChange: @escaping () -> Void,
|
||||||
tag: AnyObject?,
|
tag: AnyObject?,
|
||||||
updated: @escaping (ComponentTransition) -> Void
|
updated: @escaping (ComponentTransition) -> Void
|
||||||
) {
|
) {
|
||||||
@@ -52,6 +54,7 @@ final class GiftListItemComponent: Component {
|
|||||||
self.starGifts = starGifts
|
self.starGifts = starGifts
|
||||||
self.selectedId = selectedId
|
self.selectedId = selectedId
|
||||||
self.selectionUpdated = selectionUpdated
|
self.selectionUpdated = selectionUpdated
|
||||||
|
self.onTabChange = onTabChange
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.updated = updated
|
self.updated = updated
|
||||||
}
|
}
|
||||||
@@ -150,12 +153,17 @@ final class GiftListItemComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousGiftId = self.selectedGiftId
|
||||||
self.selectedGiftId = id
|
self.selectedGiftId = id
|
||||||
|
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
self.resaleGiftsState = nil
|
self.resaleGiftsState = nil
|
||||||
self.resaleGiftsDisposable.set(nil)
|
self.resaleGiftsDisposable.set(nil)
|
||||||
} else {
|
} else {
|
||||||
|
if previousGiftId == 0 {
|
||||||
|
component.onTabChange()
|
||||||
|
}
|
||||||
|
|
||||||
let resaleGiftsContext: ResaleGiftsContext
|
let resaleGiftsContext: ResaleGiftsContext
|
||||||
if let current = self.resaleGiftsContexts[id] {
|
if let current = self.resaleGiftsContexts[id] {
|
||||||
resaleGiftsContext = current
|
resaleGiftsContext = current
|
||||||
@@ -173,7 +181,6 @@ final class GiftListItemComponent: Component {
|
|||||||
self.resaleGiftsState = state
|
self.resaleGiftsState = state
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
let transition: ComponentTransition = isFirstTime ? .easeInOut(duration: 0.25) : .immediate
|
let transition: ComponentTransition = isFirstTime ? .easeInOut(duration: 0.25) : .immediate
|
||||||
self.state?.updated(transition: transition)
|
|
||||||
component.updated(transition)
|
component.updated(transition)
|
||||||
}
|
}
|
||||||
isFirstTime = false
|
isFirstTime = false
|
||||||
@@ -182,7 +189,6 @@ final class GiftListItemComponent: Component {
|
|||||||
|
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
let transition: ComponentTransition = .easeInOut(duration: 0.25)
|
let transition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||||
self.state?.updated(transition: transition)
|
|
||||||
component.updated(transition)
|
component.updated(transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,18 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.maskNode)
|
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 {
|
if params.isStandalone {
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
transition.updateAlpha(node: self.backgroundNode, alpha: item.showBackground ? 1.0 : 0.0)
|
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.backgroundNode.isHidden = false
|
||||||
self.topStripeNode.isHidden = true
|
self.topStripeNode.isHidden = true
|
||||||
self.bottomStripeNode.isHidden = false
|
self.bottomStripeNode.isHidden = hasBackground
|
||||||
self.maskNode.isHidden = true
|
self.maskNode.isHidden = true
|
||||||
|
|
||||||
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||||
@@ -239,14 +251,6 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
|||||||
let avatarSize: CGFloat = 104.0
|
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 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(
|
let _ = self.background.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(PeerInfoCoverComponent(
|
component: AnyComponent(PeerInfoCoverComponent(
|
||||||
@@ -314,7 +318,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
|||||||
credibilityIcon = .emojiStatus(emojiStatus)
|
credibilityIcon = .emojiStatus(emojiStatus)
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
credibilityIcon = .verified
|
credibilityIcon = .verified
|
||||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != item.context.account.peerId) {
|
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
credibilityIcon = .premium
|
credibilityIcon = .premium
|
||||||
} else {
|
} else {
|
||||||
credibilityIcon = .none
|
credibilityIcon = .none
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
|
|
||||||
public final class TransitionHint {
|
public final class TransitionHint {
|
||||||
public let animateTabChange: Bool
|
public let animateTabChange: Bool
|
||||||
|
public let forceGiftsUpdate: Bool
|
||||||
|
|
||||||
public init(animateTabChange: Bool) {
|
public init(animateTabChange: Bool = false, forceGiftsUpdate: Bool = false) {
|
||||||
self.animateTabChange = animateTabChange
|
self.animateTabChange = animateTabChange
|
||||||
|
self.forceGiftsUpdate = forceGiftsUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +175,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
private var currentSection: Section = .profile
|
private var currentSection: Section = .profile
|
||||||
|
|
||||||
|
private let previewShadowView = UIImageView(image: generatePreviewShadowImage())
|
||||||
|
|
||||||
private let profilePreview = ComponentView<Empty>()
|
private let profilePreview = ComponentView<Empty>()
|
||||||
private let profileColorSection = ComponentView<Empty>()
|
private let profileColorSection = ComponentView<Empty>()
|
||||||
private let profileResetColorSection = ComponentView<Empty>()
|
private let profileResetColorSection = ComponentView<Empty>()
|
||||||
@@ -183,7 +187,6 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
private let nameGiftsSection = ComponentView<Empty>()
|
private let nameGiftsSection = ComponentView<Empty>()
|
||||||
|
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
private var forceNextUpdate = false
|
|
||||||
|
|
||||||
private var component: UserAppearanceScreenComponent?
|
private var component: UserAppearanceScreenComponent?
|
||||||
private(set) weak var state: EmptyComponentState?
|
private(set) weak var state: EmptyComponentState?
|
||||||
@@ -246,6 +249,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
self.scrollView.alwaysBounceVertical = true
|
self.scrollView.alwaysBounceVertical = true
|
||||||
|
|
||||||
self.edgeEffectView = EdgeEffectView()
|
self.edgeEffectView = EdgeEffectView()
|
||||||
|
self.edgeEffectView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
@@ -256,6 +260,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
|
|
||||||
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
||||||
|
|
||||||
|
self.containerView.addSubview(self.previewShadowView)
|
||||||
|
|
||||||
self.addSubview(self.edgeEffectView)
|
self.addSubview(self.edgeEffectView)
|
||||||
|
|
||||||
self.backButton.action = { [weak self] _, _ in
|
self.backButton.action = { [weak self] _, _ in
|
||||||
@@ -947,12 +953,6 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var forceUpdate = false
|
|
||||||
if self.forceNextUpdate {
|
|
||||||
self.forceNextUpdate = false
|
|
||||||
forceUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let environment = environment[EnvironmentType.self].value
|
let environment = environment[EnvironmentType.self].value
|
||||||
let themeUpdated = self.environment?.theme !== environment.theme
|
let themeUpdated = self.environment?.theme !== environment.theme
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
@@ -963,8 +963,10 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
let theme = environment.theme
|
let theme = environment.theme
|
||||||
|
|
||||||
var animateTabChange = false
|
var animateTabChange = false
|
||||||
|
var forceGiftsUpdate = false
|
||||||
if let hint = transition.userData(TransitionHint.self) {
|
if let hint = transition.userData(TransitionHint.self) {
|
||||||
animateTabChange = hint.animateTabChange
|
animateTabChange = hint.animateTabChange
|
||||||
|
forceGiftsUpdate = hint.forceGiftsUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@@ -1130,14 +1132,14 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
self.selectedProfileGift = nil
|
self.selectedProfileGift = nil
|
||||||
self.updatedPeerProfileColor = nil
|
self.updatedPeerProfileColor = nil
|
||||||
self.updatedPeerProfileEmoji = nil
|
self.updatedPeerProfileEmoji = nil
|
||||||
|
self.updatedPeerStatus = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.currentSection = updatedSection
|
self.currentSection = updatedSection
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@@ -1162,6 +1164,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
|
|
||||||
let itemCornerRadius: CGFloat = 26.0
|
let itemCornerRadius: CGFloat = 26.0
|
||||||
|
|
||||||
|
transition.setTintColor(view: self.previewShadowView, color: environment.theme.list.itemBlocksBackgroundColor)
|
||||||
|
|
||||||
switch self.currentSection {
|
switch self.currentSection {
|
||||||
case .profile:
|
case .profile:
|
||||||
var transition = transition
|
var transition = transition
|
||||||
@@ -1209,7 +1213,10 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setFrame(view: profilePreviewView, frame: profilePreviewFrame)
|
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>] = []
|
var profileLogoContents: [AnyComponentWithIdentity<Empty>] = []
|
||||||
profileLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
profileLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||||
@@ -1257,12 +1264,14 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectedProfileGift != nil {
|
if self.selectedProfileGift != nil {
|
||||||
|
self.selectedProfileGift = nil
|
||||||
} else {
|
self.updatedPeerProfileColor = nil
|
||||||
|
self.updatedPeerProfileEmoji = nil
|
||||||
|
self.updatedPeerStatus = nil
|
||||||
|
}
|
||||||
self.currentSection = .name
|
self.currentSection = .name
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)),
|
)),
|
||||||
items: [
|
items: [
|
||||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor(
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor(
|
||||||
@@ -1391,8 +1400,10 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
if let status = resolvedState.emojiStatus, case let .starGift(id, _, _, _, _, _, _, _, _) = status.content {
|
if let status = resolvedState.emojiStatus, case let .starGift(id, _, _, _, _, _, _, _, _) = status.content {
|
||||||
selectedGiftId = id
|
selectedGiftId = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listTransition = transition.withUserData(ListSectionComponent.TransitionHint(forceUpdate: forceGiftsUpdate))
|
||||||
let giftsSectionSize = self.profileGiftsSection.update(
|
let giftsSectionSize = self.profileGiftsSection.update(
|
||||||
transition: transition,
|
transition: listTransition,
|
||||||
component: AnyComponent(ListSectionComponent(
|
component: AnyComponent(ListSectionComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
style: .glass,
|
style: .glass,
|
||||||
@@ -1454,13 +1465,18 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
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,
|
tag: giftListTag,
|
||||||
updated: { [weak self] transition in
|
updated: { [weak self] transition in
|
||||||
if let self {
|
if let self, !self.isUpdating {
|
||||||
self.forceNextUpdate = true
|
self.state?.updated(transition: transition.withUserData(TransitionHint(forceGiftsUpdate: true)))
|
||||||
if !self.isUpdating {
|
|
||||||
self.state?.updated(transition: transition)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1469,8 +1485,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
displaySeparators: false
|
displaySeparators: false
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
forceUpdate: forceUpdate,
|
forceUpdate: forceGiftsUpdate,
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
||||||
)
|
)
|
||||||
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
||||||
if let giftsSectionView = self.profileGiftsSection.view {
|
if let giftsSectionView = self.profileGiftsSection.view {
|
||||||
@@ -1569,7 +1585,10 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setFrame(view: namePreviewView, frame: namePreviewFrame)
|
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(
|
let nameColorSectionSize = self.nameColorSection.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@@ -1655,8 +1674,9 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listTransition = transition.withUserData(ListSectionComponent.TransitionHint(forceUpdate: forceGiftsUpdate))
|
||||||
let giftsSectionSize = self.nameGiftsSection.update(
|
let giftsSectionSize = self.nameGiftsSection.update(
|
||||||
transition: transition,
|
transition: listTransition,
|
||||||
component: AnyComponent(ListSectionComponent(
|
component: AnyComponent(ListSectionComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
style: .glass,
|
style: .glass,
|
||||||
@@ -1692,12 +1712,18 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
self.updatedPeerNameEmoji = peerColor.backgroundEmojiId
|
self.updatedPeerNameEmoji = peerColor.backgroundEmojiId
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
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,
|
tag: giftListTag,
|
||||||
updated: { [weak self] transition in
|
updated: { [weak self] transition in
|
||||||
if let self {
|
if let self, !self.isUpdating {
|
||||||
if !self.isUpdating {
|
self.state?.updated(transition: transition.withUserData(TransitionHint(forceGiftsUpdate: true)))
|
||||||
self.state?.updated(transition: transition)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1706,8 +1732,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
displaySeparators: false
|
displaySeparators: false
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
forceUpdate: forceUpdate,
|
forceUpdate: forceGiftsUpdate,
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
||||||
)
|
)
|
||||||
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
||||||
if let giftsSectionView = self.nameGiftsSection.view {
|
if let giftsSectionView = self.nameGiftsSection.view {
|
||||||
@@ -1830,14 +1856,14 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
transition.setAlpha(view: buttonView, alpha: 1.0)
|
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))
|
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)
|
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 previousBounds = self.scrollView.bounds
|
||||||
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
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 {
|
if self.scrollView.frame != scrollViewFrame {
|
||||||
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.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)
|
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)
|
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