mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 18:59:54 +00:00
Various improvements
This commit is contained in:
parent
9a414b9f09
commit
72c466ca13
@ -57,6 +57,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private let ribbonTextNode: TextNode
|
||||
|
||||
private let creatorButtonNode: HighlightTrackingButtonNode
|
||||
private var creatorButtonBackgroundNode: NavigationBackgroundNode?
|
||||
private let creatorButtonTitleNode: TextNode
|
||||
|
||||
private var shimmerEffectNode: ShimmerEffectForegroundNode?
|
||||
@ -644,6 +645,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if case let .unique(uniqueGift) = gift {
|
||||
isStarGift = true
|
||||
|
||||
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
|
||||
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
|
||||
}
|
||||
|
||||
let isSelfGift = item.message.id.peerId == item.context.account.peerId
|
||||
let authorName: String
|
||||
if isUpgrade {
|
||||
@ -831,9 +836,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
), textAlignment: .center)
|
||||
|
||||
let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
if !creatorButtonTitle.isEmpty {
|
||||
if modelTitle == nil && !creatorButtonTitle.isEmpty {
|
||||
textSpacing += 28.0
|
||||
}
|
||||
|
||||
@ -841,6 +846,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
if let _ = modelTitle {
|
||||
giftSize.height += 70.0
|
||||
|
||||
if !creatorButtonTitle.isEmpty {
|
||||
giftSize.height += 28.0
|
||||
}
|
||||
}
|
||||
|
||||
if !buttonTitle.isEmpty {
|
||||
@ -920,11 +929,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.animationNode.isHidden = isStoryEntity
|
||||
|
||||
strongSelf.buttonNode.isHidden = buttonTitle.isEmpty
|
||||
strongSelf.buttonNode.isUserInteractionEnabled = !item.presentationData.isPreview
|
||||
strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty
|
||||
|
||||
strongSelf.creatorButtonNode.isHidden = creatorButtonTitle.isEmpty
|
||||
strongSelf.creatorButtonNode.isUserInteractionEnabled = !item.presentationData.isPreview
|
||||
strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty
|
||||
|
||||
|
||||
if strongSelf.item == nil && !isStoryEntity {
|
||||
strongSelf.animationNode.started = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
@ -964,7 +975,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.labelNode.isHidden = !hasServiceMessage
|
||||
|
||||
strongSelf.buttonNode.backgroundColor = overlayColor
|
||||
strongSelf.creatorButtonNode.backgroundColor = overlayColor
|
||||
|
||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
||||
strongSelf.placeholderNode.frame = animationFrame
|
||||
@ -985,22 +995,45 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let _ = moreApply()
|
||||
let _ = creatorButtonTitleApply()
|
||||
|
||||
|
||||
|
||||
|
||||
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size)
|
||||
strongSelf.labelNode.frame = labelFrame
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
|
||||
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight))
|
||||
|
||||
var attributesOffsetY: CGFloat = 0.0
|
||||
|
||||
let creatorButtonSize = CGSize(width: creatorButtonTitleLayout.size.width + 18.0, height: 18.0)
|
||||
let creatorButtonOriginY = titleFrame.maxY + 4.0
|
||||
let creatorButtonOriginY = modelTitle == nil ? titleFrame.maxY + 4.0 : clippingTextFrame.maxY + 5.0
|
||||
let creatorButtonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize)
|
||||
if !creatorButtonTitle.isEmpty {
|
||||
attributesOffsetY += creatorButtonSize.height + 10.0
|
||||
}
|
||||
if !strongSelf.creatorButtonNode.isHidden, modelTitle != nil {
|
||||
strongSelf.creatorButtonNode.backgroundColor = .clear
|
||||
|
||||
let creatorButtonBackgroundNode: NavigationBackgroundNode
|
||||
if let current = strongSelf.creatorButtonBackgroundNode {
|
||||
creatorButtonBackgroundNode = current
|
||||
} else {
|
||||
creatorButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.12), enableBlur: true, enableSaturation: false)
|
||||
strongSelf.creatorButtonNode.insertSubnode(creatorButtonBackgroundNode, at: 0)
|
||||
strongSelf.creatorButtonBackgroundNode = creatorButtonBackgroundNode
|
||||
}
|
||||
creatorButtonBackgroundNode.update(size: creatorButtonFrame.size, cornerRadius: 9.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.creatorButtonNode.backgroundColor = overlayColor
|
||||
|
||||
if let creatorButtonBackgroundNode = strongSelf.creatorButtonBackgroundNode {
|
||||
strongSelf.creatorButtonBackgroundNode = nil
|
||||
creatorButtonBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
strongSelf.creatorButtonTitleNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 1.0), size: creatorButtonTitleLayout.size)
|
||||
|
||||
animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize), completion: nil)
|
||||
|
||||
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight))
|
||||
animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: creatorButtonFrame, completion: nil)
|
||||
|
||||
let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size)
|
||||
strongSelf.subtitleNode.textNode.frame = subtitleFrame
|
||||
@ -1084,7 +1117,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
let _ = titleApply()
|
||||
titleTextNode.frame = CGRect(
|
||||
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + yOffset),
|
||||
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + attributesOffsetY + yOffset),
|
||||
size: titleLayout.size
|
||||
)
|
||||
}
|
||||
@ -1094,7 +1127,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
let _ = valueApply()
|
||||
valueTextNode.frame = CGRect(
|
||||
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + yOffset),
|
||||
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + attributesOffsetY + yOffset),
|
||||
size: valueLayout.size
|
||||
)
|
||||
}
|
||||
@ -1125,7 +1158,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
|
||||
var buttonOriginY = clippingTextFrame.maxY + 10.0
|
||||
if modelTitleLayoutAndApply != nil {
|
||||
buttonOriginY = clippingTextFrame.maxY + 80.0
|
||||
buttonOriginY = clippingTextFrame.maxY + attributesOffsetY + 80.0
|
||||
}
|
||||
strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 8.0), size: buttonTitleLayout.size)
|
||||
|
||||
|
||||
@ -294,7 +294,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
var contentSize = CGSize(width: params.width, height: 4.0 + 4.0)
|
||||
contentSize.height = 346.0
|
||||
contentSize.height = 370.0
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
if params.width <= 320.0 {
|
||||
insets.top = 0.0
|
||||
|
||||
@ -559,25 +559,42 @@ final class GiftSetupScreenComponent: Component {
|
||||
self.hideName = true
|
||||
}
|
||||
|
||||
var releasedBy: EnginePeer.Id?
|
||||
if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil {
|
||||
self.includeUpgrade = true
|
||||
}
|
||||
if case let .starGift(gift, _) = component.subject {
|
||||
releasedBy = gift.releasedBy
|
||||
}
|
||||
|
||||
let _ = (component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer, sendPaidMessageStars in
|
||||
var peerIds: [EnginePeer.Id] = [
|
||||
component.context.account.peerId,
|
||||
component.peerId
|
||||
]
|
||||
if let releasedBy {
|
||||
peerIds.append(releasedBy)
|
||||
}
|
||||
|
||||
let _ = combineLatest(queue: Queue.mainQueue(),
|
||||
component.context.engine.data.get(EngineDataMap(
|
||||
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
|
||||
return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
}
|
||||
)),
|
||||
component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId)
|
||||
)
|
||||
).start(next: { [weak self] peers, sendPaidMessageStars in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let peer {
|
||||
self.peerMap[peer.id] = peer
|
||||
}
|
||||
if let accountPeer {
|
||||
self.peerMap[accountPeer.id] = accountPeer
|
||||
var peersMap: [EnginePeer.Id: EnginePeer] = [:]
|
||||
for (peerId, maybePeer) in peers {
|
||||
if let peer = maybePeer {
|
||||
peersMap[peerId] = peer
|
||||
}
|
||||
}
|
||||
self.peerMap = peersMap
|
||||
self.sendPaidMessageStars = sendPaidMessageStars
|
||||
|
||||
self.state?.updated()
|
||||
@ -847,7 +864,7 @@ final class GiftSetupScreenComponent: Component {
|
||||
let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
|
||||
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 370.0, tag: self.introPlaceholderTag))))
|
||||
|
||||
if self.sendPaidMessageStars == nil {
|
||||
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
|
||||
@ -965,6 +982,7 @@ final class GiftSetupScreenComponent: Component {
|
||||
if let accountPeer = self.peerMap[component.context.account.peerId] {
|
||||
var upgradeStars: Int64?
|
||||
let subject: ChatGiftPreviewItem.Subject
|
||||
var releasedBy: EnginePeer.Id?
|
||||
switch component.subject {
|
||||
case let .premium(product):
|
||||
if self.payWithStars, let starsPrice = product.starsPrice {
|
||||
@ -976,12 +994,16 @@ final class GiftSetupScreenComponent: Component {
|
||||
case let .starGift(gift, _):
|
||||
subject = .starGift(gift: gift)
|
||||
upgradeStars = gift.upgradeStars
|
||||
releasedBy = gift.releasedBy
|
||||
}
|
||||
|
||||
var peers: [EnginePeer] = [accountPeer]
|
||||
if let peer = self.peerMap[component.peerId] {
|
||||
peers.append(peer)
|
||||
}
|
||||
if let releasedBy, let peer = self.peerMap[releasedBy] {
|
||||
peers.append(peer)
|
||||
}
|
||||
|
||||
let introContentSize = self.introContent.update(
|
||||
transition: transition,
|
||||
|
||||
@ -1535,7 +1535,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let subtitle = Child(MultilineTextComponent.self)
|
||||
|
||||
let descriptionButton = Child(RoundedRectangle.self)
|
||||
let descriptionButton = Child(PlainButtonComponent.self)
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
||||
let transferButton = Child(PlainButtonComponent.self)
|
||||
@ -1602,6 +1602,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
var isSelfGift = false
|
||||
var isChannelGift = false
|
||||
var isMyUniqueGift = false
|
||||
var releasedByPeer: EnginePeer?
|
||||
|
||||
if case let .soldOutGift(gift) = subject {
|
||||
animationFile = gift.file
|
||||
@ -1618,6 +1619,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
case let .generic(gift):
|
||||
if let releasedBy = gift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
|
||||
subtitleString = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string
|
||||
releasedByPeer = peer
|
||||
}
|
||||
|
||||
animationFile = gift.file
|
||||
@ -2167,6 +2169,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
|
||||
descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string
|
||||
hasDescriptionButton = true
|
||||
releasedByPeer = peer
|
||||
}
|
||||
} else if soldOut {
|
||||
descriptionText = strings.Gift_View_UnavailableDescription
|
||||
@ -2235,16 +2238,34 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
|
||||
var descriptionOffset: CGFloat = 0.0
|
||||
if let subtitleString {
|
||||
let textColor = theme.actionSheet.secondaryTextColor
|
||||
let textFont = Font.regular(13.0)
|
||||
let subtitleAttributedString = parseMarkdownIntoAttributedString(
|
||||
subtitleString,
|
||||
attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.controlAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}),
|
||||
textAlignment: .center
|
||||
)
|
||||
|
||||
let subtitle = subtitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: subtitleString,
|
||||
font: Font.regular(13.0),
|
||||
textColor: theme.actionSheet.secondaryTextColor,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
text: .plain(subtitleAttributedString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
maximumNumberOfLines: 1,
|
||||
highlightColor: theme.actionSheet.controlAccentColor.withAlphaComponent(0.1),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak state] attributes, _ in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let peer = releasedByPeer {
|
||||
state?.openPeer(peer)
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
@ -2275,7 +2296,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
if let _ = uniqueGift {
|
||||
textFont = Font.regular(13.0)
|
||||
if hasDescriptionButton {
|
||||
textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.5)
|
||||
textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.4)
|
||||
} else {
|
||||
textColor = vibrantColor
|
||||
}
|
||||
@ -2307,14 +2328,14 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
highlightColor: linkColor.withAlphaComponent(0.1),
|
||||
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak state] attributes, _ in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
state?.openStarsIntro()
|
||||
}
|
||||
}
|
||||
@ -2322,10 +2343,26 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(description
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
|
||||
if hasDescriptionButton {
|
||||
let descriptionButton = descriptionButton.update(
|
||||
component: RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5),
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
action: { [weak state] in
|
||||
if let releasedByPeer {
|
||||
state?.openPeer(releasedByPeer)
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: description.size.width + 18.0, height: 19.0),
|
||||
transition: .immediate
|
||||
@ -2337,12 +2374,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
)
|
||||
}
|
||||
|
||||
context.add(description
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
|
||||
originY += descriptionOffset
|
||||
|
||||
if uniqueGift != nil {
|
||||
|
||||
@ -588,6 +588,8 @@ private let textUrlEdgeCharacters: CharacterSet = {
|
||||
var set: CharacterSet = .alphanumerics
|
||||
set.formUnion(.symbols)
|
||||
set.formUnion(.punctuationCharacters)
|
||||
set.remove("(")
|
||||
set.remove(")")
|
||||
return set
|
||||
}()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user