mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Pinned messages improvements
This commit is contained in:
parent
de7ca018f6
commit
cd96e69361
@ -69,6 +69,11 @@ debug_deps = select({
|
||||
"//conditions:default": [],
|
||||
})
|
||||
|
||||
strip_framework = select({
|
||||
":debug": None,
|
||||
"//conditions:default": ":StripFramework",
|
||||
})
|
||||
|
||||
filegroup(
|
||||
name = "AppResources",
|
||||
srcs = glob([
|
||||
@ -542,7 +547,7 @@ ios_framework(
|
||||
":MtProtoKitInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/MtProtoKit:MtProtoKit",
|
||||
],
|
||||
@ -583,7 +588,7 @@ ios_framework(
|
||||
":SwiftSignalKitInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
],
|
||||
@ -627,7 +632,7 @@ ios_framework(
|
||||
":SwiftSignalKitFramework",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/Postbox:Postbox",
|
||||
],
|
||||
@ -668,7 +673,7 @@ ios_framework(
|
||||
":TelegramApiInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/TelegramApi:TelegramApi",
|
||||
],
|
||||
@ -713,7 +718,7 @@ ios_framework(
|
||||
":PostboxFramework",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
],
|
||||
@ -761,7 +766,7 @@ ios_framework(
|
||||
":TelegramApiFramework",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
],
|
||||
@ -802,7 +807,7 @@ ios_framework(
|
||||
":AsyncDisplayKitInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
],
|
||||
@ -863,7 +868,7 @@ ios_framework(
|
||||
":AsyncDisplayKitFramework",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
],
|
||||
@ -914,7 +919,7 @@ ios_framework(
|
||||
":DisplayFramework",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
ipa_post_processor = ":StripFramework",
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/TelegramUI:TelegramUI",
|
||||
] + debug_deps,
|
||||
|
@ -5822,3 +5822,8 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"ChatList.MessageMusic_1" = "1 Music File";
|
||||
"ChatList.MessageMusic_any" = "%@ Music Files";
|
||||
|
||||
"Conversation.PinOlderMessageAlertTitle" = "Pin Message";
|
||||
"Conversation.PinOlderMessageAlertText" = "Do you want to pin an older message while leaving a more recent one pinned?";
|
||||
"Conversation.PinMessageAlertPin" = "Pin";
|
||||
|
||||
|
@ -140,7 +140,7 @@ final class HistoryViewStateValidationContexts {
|
||||
|
||||
func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput? = nil) {
|
||||
assert(self.queue.isCurrent())
|
||||
guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.music else {
|
||||
guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.music || view.tagMask == MessageTags.pinned else {
|
||||
if self.contexts[id] != nil {
|
||||
self.contexts.removeValue(forKey: id)
|
||||
}
|
||||
|
@ -50,7 +50,37 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, UpdatePinnedMessageError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
return account.postbox.transaction { _ in
|
||||
return account.postbox.transaction { transaction in
|
||||
switch updates {
|
||||
case let .updates(updates, _, _, _, _):
|
||||
if updates.isEmpty {
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
let messageId: MessageId
|
||||
switch update {
|
||||
case let .pin(id, _):
|
||||
messageId = id
|
||||
case let .clear(id):
|
||||
messageId = id
|
||||
}
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
var updatedTags = currentMessage.tags
|
||||
switch update {
|
||||
case .pin:
|
||||
updatedTags.insert(.pinned)
|
||||
case .clear:
|
||||
updatedTags.remove(.pinned)
|
||||
}
|
||||
if updatedTags == currentMessage.tags {
|
||||
return .skip
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
/*transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
let pinnedMessageId: MessageId?
|
||||
@ -111,6 +141,37 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
|
||||
|> mapToSignal { updates -> Signal<Void, UpdatePinnedMessageError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
return account.postbox.transaction { transaction in
|
||||
switch updates {
|
||||
case let .updates(updates, _, _, _, _):
|
||||
if updates.isEmpty {
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
let messageId: MessageId
|
||||
switch update {
|
||||
case let .pin(id, _):
|
||||
messageId = id
|
||||
case let .clear(id):
|
||||
messageId = id
|
||||
}
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
var updatedTags = currentMessage.tags
|
||||
switch update {
|
||||
case .pin:
|
||||
updatedTags.insert(.pinned)
|
||||
case .clear:
|
||||
updatedTags.remove(.pinned)
|
||||
}
|
||||
if updatedTags == currentMessage.tags {
|
||||
return .skip
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let _ = peer as? TelegramGroup {
|
||||
let current = current as? CachedGroupData ?? CachedGroupData()
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -3233,6 +3233,64 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, hasEmbeddedTitleContent: self.hasEmbeddedTitleContent)
|
||||
}
|
||||
|
||||
private func topPinnedMessageSignal(latest: Bool) -> Signal<ChatPinnedMessage?, NoError> {
|
||||
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
|
||||
switch self.chatLocation {
|
||||
case let .peer(peerId) where peerId.namespace == Namespaces.Peer.CloudChannel:
|
||||
let replyHistory: Signal<ChatHistoryViewUpdate, NoError> = (chatHistoryViewForLocation(ChatHistoryLocationInput(content: .Initial(count: 100), id: 0), context: self.context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, additionalData: [])
|
||||
|> castError(Bool.self)
|
||||
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
|
||||
switch update {
|
||||
case let .Loading(_, type):
|
||||
if case .Generic(.FillHole) = type {
|
||||
return .fail(true)
|
||||
}
|
||||
case let .HistoryView(_, type, _, _, _, _, _):
|
||||
if case .Generic(.FillHole) = type {
|
||||
return .fail(true)
|
||||
}
|
||||
}
|
||||
return .single(update)
|
||||
})
|
||||
|> restartIfError
|
||||
|
||||
topPinnedMessage = combineLatest(
|
||||
replyHistory,
|
||||
latest ? .single(nil) : self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
|
||||
)
|
||||
|> map { update, topVisibleMessageRange -> ChatPinnedMessage? in
|
||||
var message: ChatPinnedMessage?
|
||||
switch update {
|
||||
case .Loading:
|
||||
break
|
||||
case let .HistoryView(view, _, _, _, _, _, _):
|
||||
for i in 0 ..< view.entries.count {
|
||||
let entry = view.entries[i]
|
||||
var matches = false
|
||||
if message == nil {
|
||||
matches = true
|
||||
} else if let topVisibleMessageRange = topVisibleMessageRange {
|
||||
if entry.message.id < topVisibleMessageRange.upperBound {
|
||||
matches = true
|
||||
}
|
||||
} else {
|
||||
matches = true
|
||||
}
|
||||
if matches {
|
||||
message = ChatPinnedMessage(message: entry.message, isLatest: i == view.entries.count - 1)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return message
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
default:
|
||||
topPinnedMessage = .single(nil)
|
||||
}
|
||||
return topPinnedMessage
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self)
|
||||
|
||||
@ -3402,60 +3460,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let isTopReplyThreadMessageShown: Signal<Bool, NoError> = self.chatDisplayNode.historyNode.isTopReplyThreadMessageShown.get()
|
||||
|> distinctUntilChanged
|
||||
|
||||
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
|
||||
switch self.chatLocation {
|
||||
case let .peer(peerId) where peerId.namespace == Namespaces.Peer.CloudChannel:
|
||||
let replyHistory: Signal<ChatHistoryViewUpdate, NoError> = (chatHistoryViewForLocation(ChatHistoryLocationInput(content: .Initial(count: 100), id: 0), context: self.context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, additionalData: [])
|
||||
|> castError(Bool.self)
|
||||
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
|
||||
switch update {
|
||||
case let .Loading(_, type):
|
||||
if case .Generic(.FillHole) = type {
|
||||
return .fail(true)
|
||||
}
|
||||
case let .HistoryView(_, type, _, _, _, _, _):
|
||||
if case .Generic(.FillHole) = type {
|
||||
return .fail(true)
|
||||
}
|
||||
}
|
||||
return .single(update)
|
||||
})
|
||||
|> restartIfError
|
||||
|
||||
topPinnedMessage = combineLatest(
|
||||
replyHistory,
|
||||
self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
|
||||
)
|
||||
|> map { update, topVisibleMessageRange -> ChatPinnedMessage? in
|
||||
var message: ChatPinnedMessage?
|
||||
switch update {
|
||||
case .Loading:
|
||||
break
|
||||
case let .HistoryView(view, _, _, _, _, _, _):
|
||||
for i in 0 ..< view.entries.count {
|
||||
let entry = view.entries[i]
|
||||
var matches = false
|
||||
if message == nil {
|
||||
matches = true
|
||||
} else if let topVisibleMessageRange = topVisibleMessageRange {
|
||||
if entry.message.id < topVisibleMessageRange.lowerBound {
|
||||
matches = true
|
||||
}
|
||||
} else {
|
||||
matches = true
|
||||
}
|
||||
if matches {
|
||||
message = ChatPinnedMessage(message: entry.message, isLatest: i == view.entries.count - 1)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return message
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
default:
|
||||
topPinnedMessage = .single(nil)
|
||||
}
|
||||
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> = self.topPinnedMessageSignal(latest: false)
|
||||
|
||||
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage in
|
||||
if let strongSelf = self {
|
||||
@ -3586,7 +3591,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if case .replyThread = self.chatLocation {
|
||||
effectiveCachedDataReady = self.cachedDataReady.get()
|
||||
} else {
|
||||
effectiveCachedDataReady = .single(true)
|
||||
//effectiveCachedDataReady = .single(true)
|
||||
effectiveCachedDataReady = self.cachedDataReady.get()
|
||||
}
|
||||
self.ready.set(combineLatest(self.chatDisplayNode.historyNode.historyState.get(), self._chatLocationInfoReady.get(), effectiveCachedDataReady, initialData) |> map { _, chatLocationInfoReady, cachedDataReady, _ in
|
||||
return chatLocationInfoReady && cachedDataReady
|
||||
@ -4812,11 +4818,46 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if pinImmediately {
|
||||
pinAction(true)
|
||||
} else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_PinMessageAlertGroup, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlert_OnlyPin, action: {
|
||||
pinAction(false)
|
||||
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
|
||||
pinAction(true)
|
||||
})]), in: .window(.root))
|
||||
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> = strongSelf.topPinnedMessageSignal(latest: true)
|
||||
|> take(1)
|
||||
|
||||
let _ = (topPinnedMessage
|
||||
|> deliverOnMainQueue).start(next: { value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let title: String?
|
||||
let text: String
|
||||
let actionLayout: TextAlertContentActionLayout
|
||||
let actions: [TextAlertAction]
|
||||
if let value = value, value.message.id > messageId {
|
||||
title = strongSelf.presentationData.strings.Conversation_PinOlderMessageAlertTitle
|
||||
text = strongSelf.presentationData.strings.Conversation_PinOlderMessageAlertText
|
||||
actionLayout = .vertical
|
||||
actions = [
|
||||
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlertPin, action: {
|
||||
pinAction(false)
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||
})
|
||||
]
|
||||
} else {
|
||||
title = nil
|
||||
text = strongSelf.presentationData.strings.Conversation_PinMessageAlertGroup
|
||||
actionLayout = .horizontal
|
||||
actions = [
|
||||
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlert_OnlyPin, action: {
|
||||
pinAction(false)
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
|
||||
pinAction(true)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: title, text: text, actions: actions, actionLayout: actionLayout), in: .window(.root))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let pinnedMessageId = strongSelf.presentationInterfaceState.pinnedMessage?.message.id {
|
||||
@ -4854,7 +4895,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if canManagePin {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UnpinMessageAlert, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_Unpin, action: {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UnpinMessageAlert, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_Unpin, action: {
|
||||
if let strongSelf = self {
|
||||
let disposable: MetaDisposable
|
||||
if let current = strongSelf.unpinMessageDisposable {
|
||||
@ -4865,7 +4906,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
disposable.set(requestUpdatePinnedMessage(account: strongSelf.context.account, peerId: peer.id, update: .clear(id: id)).start())
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root))
|
||||
} else {
|
||||
if let pinnedMessage = strongSelf.presentationInterfaceState.pinnedMessage {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
|
@ -643,27 +643,37 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
}
|
||||
|
||||
if data.canPin, case .peer = chatPresentationInterfaceState.chatLocation {
|
||||
let isPinned: Bool
|
||||
var pinnedSelectedMessageId: MessageId?
|
||||
if let _ = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
isPinned = messages[0].tags.contains(.pinned)
|
||||
for message in messages {
|
||||
if message.tags.contains(.pinned) {
|
||||
pinnedSelectedMessageId = message.id
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isPinned = chatPresentationInterfaceState.pinnedMessage?.message.id == messages[0].id
|
||||
for message in messages {
|
||||
if chatPresentationInterfaceState.pinnedMessage?.message.id == message.id {
|
||||
pinnedSelectedMessageId = message.id
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isPinned {
|
||||
if let pinnedSelectedMessageId = pinnedSelectedMessageId {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Unpin, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unpin"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.unpinMessage(pinnedSelectedMessageId)
|
||||
f(.default)
|
||||
})))
|
||||
} else {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.pinMessage(messages[0].id)
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
} else {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Unpin, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unpin"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.unpinMessage(messages[0].id)
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user