mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-26 21:20:43 +00:00
Monoforums
This commit is contained in:
parent
da477ec84e
commit
7b72c1a034
@ -933,7 +933,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else if case let .legacyGroup(group) = peer {
|
} else if case let .legacyGroup(group) = peer {
|
||||||
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
} else if case let .channel(channel) = peer {
|
} else if case let .channel(channel) = peer {
|
||||||
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
if case let .channel(mainChannel) = chatPeer, mainChannel.isMonoForum {
|
||||||
|
titleAttributedString = NSAttributedString(string: item.presentationData.strings.Monoforum_NameFormat(channel.title).string, font: titleBoldFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
|
} else {
|
||||||
|
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch item.status {
|
switch item.status {
|
||||||
|
@ -3240,6 +3240,22 @@ public extension Api.functions.channels {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.channels {
|
||||||
|
static func getMessageAuthor(channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.User>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-320691994)
|
||||||
|
channel.serialize(buffer, true)
|
||||||
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "channels.getMessageAuthor", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.User?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.User
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.channels {
|
public extension Api.functions.channels {
|
||||||
static func getMessages(channel: Api.InputChannel, id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
static func getMessages(channel: Api.InputChannel, id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
@ -1575,5 +1575,9 @@ public extension TelegramEngine {
|
|||||||
return filteredResult
|
return filteredResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func requestMessageAuthor(id: EngineMessage.Id) -> Signal<EnginePeer?, NoError> {
|
||||||
|
return _internal_requestMessageAuthor(account: self.account, id: id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,3 +156,31 @@ func _internal_searchLocalSavedMessagesPeers(account: Account, query: String, in
|
|||||||
return transaction.searchSubPeers(peerId: account.peerId, query: query, indexNameMapping: indexNameMapping).map(EnginePeer.init)
|
return transaction.searchSubPeers(peerId: account.peerId, query: query, indexNameMapping: indexNameMapping).map(EnginePeer.init)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _internal_requestMessageAuthor(account: Account, id: EngineMessage.Id) -> Signal<EnginePeer?, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||||
|
return transaction.getPeer(id.peerId).flatMap(apiInputChannel)
|
||||||
|
}
|
||||||
|
|> mapToSignal { inputChannel -> Signal<EnginePeer?, NoError> in
|
||||||
|
guard let inputChannel else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
if id.namespace != Namespaces.Message.Cloud {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return account.network.request(Api.functions.channels.getMessageAuthor(channel: inputChannel, id: id.id))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.User?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { user -> Signal<EnginePeer?, NoError> in
|
||||||
|
guard let user else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return account.postbox.transaction { transaction -> EnginePeer? in
|
||||||
|
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: [user]))
|
||||||
|
return transaction.getPeer(user.peerId).flatMap(EnginePeer.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -85,7 +85,7 @@ public struct PresentationResourcesSettings {
|
|||||||
public static let balance = renderIcon(name: "Settings/Menu/Balance", scaleFactor: 0.97, backgroundColors: [UIColor(rgb: 0x34c759)])
|
public static let balance = renderIcon(name: "Settings/Menu/Balance", scaleFactor: 0.97, backgroundColors: [UIColor(rgb: 0x34c759)])
|
||||||
public static let affiliateProgram = renderIcon(name: "Settings/Menu/AffiliateProgram")
|
public static let affiliateProgram = renderIcon(name: "Settings/Menu/AffiliateProgram")
|
||||||
public static let earnStars = renderIcon(name: "Settings/Menu/EarnStars")
|
public static let earnStars = renderIcon(name: "Settings/Menu/EarnStars")
|
||||||
public static let channelMessages = renderIcon(name: "Chat/Info/ChannelMessages", backgroundColors: [UIColor(rgb: 0xFF9500)])
|
public static let channelMessages = renderIcon(name: "Chat/Info/ChannelMessages", backgroundColors: [UIColor(rgb: 0x5856D6)])
|
||||||
|
|
||||||
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
@ -1041,212 +1041,230 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
case changeInfo
|
case changeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var allConfigItems: [(ConfigItem, Bool)] = []
|
if case let .channel(channel) = component.chatPeer, channel.isMonoForum {
|
||||||
if !self.allowedMediaRights.isEmpty || !self.allowedParticipantRights.isEmpty {
|
} else {
|
||||||
for configItem in ConfigItem.allCases {
|
var allConfigItems: [(ConfigItem, Bool)] = []
|
||||||
let isEnabled: Bool
|
if !self.allowedMediaRights.isEmpty || !self.allowedParticipantRights.isEmpty {
|
||||||
|
for configItem in ConfigItem.allCases {
|
||||||
|
let isEnabled: Bool
|
||||||
|
switch configItem {
|
||||||
|
case .sendMessages:
|
||||||
|
isEnabled = self.allowedParticipantRights.contains(.sendMessages)
|
||||||
|
case .sendMedia:
|
||||||
|
isEnabled = !self.allowedMediaRights.isEmpty
|
||||||
|
case .addUsers:
|
||||||
|
isEnabled = self.allowedParticipantRights.contains(.addMembers)
|
||||||
|
case .pinMessages:
|
||||||
|
isEnabled = self.allowedParticipantRights.contains(.pinMessages)
|
||||||
|
case .changeInfo:
|
||||||
|
isEnabled = self.allowedParticipantRights.contains(.changeInfo)
|
||||||
|
}
|
||||||
|
allConfigItems.append((configItem, isEnabled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop: for (configItem, isEnabled) in allConfigItems {
|
||||||
|
let itemTitle: AnyComponent<Empty>
|
||||||
|
let itemValue: Bool
|
||||||
switch configItem {
|
switch configItem {
|
||||||
case .sendMessages:
|
case .sendMessages:
|
||||||
isEnabled = self.allowedParticipantRights.contains(.sendMessages)
|
itemTitle = AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: environment.strings.Channel_BanUser_PermissionSendMessages,
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 1
|
||||||
|
))
|
||||||
|
itemValue = self.participantRights.contains(.sendMessages)
|
||||||
case .sendMedia:
|
case .sendMedia:
|
||||||
isEnabled = !self.allowedMediaRights.isEmpty
|
if isEnabled {
|
||||||
case .addUsers:
|
itemTitle = AnyComponent(HStack([
|
||||||
isEnabled = self.allowedParticipantRights.contains(.addMembers)
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||||
case .pinMessages:
|
text: .plain(NSAttributedString(
|
||||||
isEnabled = self.allowedParticipantRights.contains(.pinMessages)
|
string: environment.strings.Channel_BanUser_PermissionSendMedia,
|
||||||
case .changeInfo:
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
isEnabled = self.allowedParticipantRights.contains(.changeInfo)
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
}
|
)),
|
||||||
allConfigItems.append((configItem, isEnabled))
|
maximumNumberOfLines: 1
|
||||||
}
|
))),
|
||||||
}
|
AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent(
|
||||||
|
theme: environment.theme,
|
||||||
loop: for (configItem, isEnabled) in allConfigItems {
|
title: "\(self.mediaRights.count)/\(self.allowedMediaRights.count)",
|
||||||
let itemTitle: AnyComponent<Empty>
|
isExpanded: self.isMediaSectionExpanded
|
||||||
let itemValue: Bool
|
)))
|
||||||
switch configItem {
|
], spacing: 7.0))
|
||||||
case .sendMessages:
|
} else {
|
||||||
itemTitle = AnyComponent(MultilineTextComponent(
|
itemTitle = AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.Channel_BanUser_PermissionSendMessages,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 1
|
|
||||||
))
|
|
||||||
itemValue = self.participantRights.contains(.sendMessages)
|
|
||||||
case .sendMedia:
|
|
||||||
if isEnabled {
|
|
||||||
itemTitle = AnyComponent(HStack([
|
|
||||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: environment.strings.Channel_BanUser_PermissionSendMedia,
|
string: environment.strings.Channel_BanUser_PermissionSendMedia,
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
)),
|
)),
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
))),
|
))
|
||||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent(
|
}
|
||||||
theme: environment.theme,
|
|
||||||
title: "\(self.mediaRights.count)/\(self.allowedMediaRights.count)",
|
itemValue = !self.mediaRights.isEmpty
|
||||||
isExpanded: self.isMediaSectionExpanded
|
case .addUsers:
|
||||||
)))
|
|
||||||
], spacing: 7.0))
|
|
||||||
} else {
|
|
||||||
itemTitle = AnyComponent(MultilineTextComponent(
|
itemTitle = AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: environment.strings.Channel_BanUser_PermissionSendMedia,
|
string: environment.strings.Channel_BanUser_PermissionAddMembers,
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
)),
|
)),
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
))
|
))
|
||||||
|
itemValue = self.participantRights.contains(.addMembers)
|
||||||
|
case .pinMessages:
|
||||||
|
itemTitle = AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: environment.strings.Channel_EditAdmin_PermissionPinMessages,
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 1
|
||||||
|
))
|
||||||
|
itemValue = self.participantRights.contains(.pinMessages)
|
||||||
|
case .changeInfo:
|
||||||
|
itemTitle = AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: environment.strings.Channel_BanUser_PermissionChangeGroupInfo,
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 1
|
||||||
|
))
|
||||||
|
itemValue = self.participantRights.contains(.changeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemValue = !self.mediaRights.isEmpty
|
configSectionItems.append(AnyComponentWithIdentity(id: configItem, component: AnyComponent(ListActionItemComponent(
|
||||||
case .addUsers:
|
theme: environment.theme,
|
||||||
itemTitle = AnyComponent(MultilineTextComponent(
|
title: itemTitle,
|
||||||
text: .plain(NSAttributedString(
|
accessory: .toggle(ListActionItemComponent.Toggle(
|
||||||
string: environment.strings.Channel_BanUser_PermissionAddMembers,
|
style: isEnabled ? .icons : .lock,
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
isOn: itemValue,
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
isInteractive: isEnabled,
|
||||||
)),
|
action: isEnabled ? { [weak self] _ in
|
||||||
maximumNumberOfLines: 1
|
guard let self else {
|
||||||
))
|
return
|
||||||
itemValue = self.participantRights.contains(.addMembers)
|
}
|
||||||
case .pinMessages:
|
|
||||||
itemTitle = AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.Channel_EditAdmin_PermissionPinMessages,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 1
|
|
||||||
))
|
|
||||||
itemValue = self.participantRights.contains(.pinMessages)
|
|
||||||
case .changeInfo:
|
|
||||||
itemTitle = AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.Channel_BanUser_PermissionChangeGroupInfo,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 1
|
|
||||||
))
|
|
||||||
itemValue = self.participantRights.contains(.changeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
configSectionItems.append(AnyComponentWithIdentity(id: configItem, component: AnyComponent(ListActionItemComponent(
|
switch configItem {
|
||||||
theme: environment.theme,
|
case .sendMessages:
|
||||||
title: itemTitle,
|
if self.participantRights.contains(.sendMessages) {
|
||||||
accessory: .toggle(ListActionItemComponent.Toggle(
|
self.participantRights.remove(.sendMessages)
|
||||||
style: isEnabled ? .icons : .lock,
|
} else {
|
||||||
isOn: itemValue,
|
self.participantRights.insert(.sendMessages)
|
||||||
isInteractive: isEnabled,
|
}
|
||||||
action: isEnabled ? { [weak self] _ in
|
case .sendMedia:
|
||||||
guard let self else {
|
if self.mediaRights.isEmpty {
|
||||||
|
self.mediaRights = self.allowedMediaRights
|
||||||
|
} else {
|
||||||
|
self.mediaRights = []
|
||||||
|
}
|
||||||
|
case .addUsers:
|
||||||
|
if self.participantRights.contains(.addMembers) {
|
||||||
|
self.participantRights.remove(.addMembers)
|
||||||
|
} else {
|
||||||
|
self.participantRights.insert(.addMembers)
|
||||||
|
}
|
||||||
|
case .pinMessages:
|
||||||
|
if self.participantRights.contains(.pinMessages) {
|
||||||
|
self.participantRights.remove(.pinMessages)
|
||||||
|
} else {
|
||||||
|
self.participantRights.insert(.pinMessages)
|
||||||
|
}
|
||||||
|
case .changeInfo:
|
||||||
|
if self.participantRights.contains(.changeInfo) {
|
||||||
|
self.participantRights.remove(.changeInfo)
|
||||||
|
} else {
|
||||||
|
self.participantRights.insert(.changeInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.35))
|
||||||
|
} : nil
|
||||||
|
)),
|
||||||
|
action: ((isEnabled && configItem == .sendMedia) || !isEnabled) ? { [weak self] _ in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !isEnabled {
|
||||||
switch configItem {
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
case .sendMessages:
|
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.GroupPermission_PermissionDisabledByDefault, actions: [
|
||||||
if self.participantRights.contains(.sendMessages) {
|
TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {
|
||||||
self.participantRights.remove(.sendMessages)
|
})
|
||||||
} else {
|
]), in: .window(.root))
|
||||||
self.participantRights.insert(.sendMessages)
|
} else {
|
||||||
}
|
self.isMediaSectionExpanded = !self.isMediaSectionExpanded
|
||||||
case .sendMedia:
|
self.state?.updated(transition: .spring(duration: 0.35))
|
||||||
if self.mediaRights.isEmpty {
|
|
||||||
self.mediaRights = self.allowedMediaRights
|
|
||||||
} else {
|
|
||||||
self.mediaRights = []
|
|
||||||
}
|
|
||||||
case .addUsers:
|
|
||||||
if self.participantRights.contains(.addMembers) {
|
|
||||||
self.participantRights.remove(.addMembers)
|
|
||||||
} else {
|
|
||||||
self.participantRights.insert(.addMembers)
|
|
||||||
}
|
|
||||||
case .pinMessages:
|
|
||||||
if self.participantRights.contains(.pinMessages) {
|
|
||||||
self.participantRights.remove(.pinMessages)
|
|
||||||
} else {
|
|
||||||
self.participantRights.insert(.pinMessages)
|
|
||||||
}
|
|
||||||
case .changeInfo:
|
|
||||||
if self.participantRights.contains(.changeInfo) {
|
|
||||||
self.participantRights.remove(.changeInfo)
|
|
||||||
} else {
|
|
||||||
self.participantRights.insert(.changeInfo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: .spring(duration: 0.35))
|
} : nil,
|
||||||
} : nil
|
highlighting: .disabled
|
||||||
)),
|
))))
|
||||||
action: ((isEnabled && configItem == .sendMedia) || !isEnabled) ? { [weak self] _ in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !isEnabled {
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.GroupPermission_PermissionDisabledByDefault, actions: [
|
|
||||||
TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {
|
|
||||||
})
|
|
||||||
]), in: .window(.root))
|
|
||||||
} else {
|
|
||||||
self.isMediaSectionExpanded = !self.isMediaSectionExpanded
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.35))
|
|
||||||
}
|
|
||||||
} : nil,
|
|
||||||
highlighting: .disabled
|
|
||||||
))))
|
|
||||||
|
|
||||||
if isEnabled, case .sendMedia = configItem, self.isMediaSectionExpanded {
|
if isEnabled, case .sendMedia = configItem, self.isMediaSectionExpanded {
|
||||||
var mediaItems: [AnyComponentWithIdentity<Empty>] = []
|
var mediaItems: [AnyComponentWithIdentity<Empty>] = []
|
||||||
mediaRightsLoop: for possibleMediaItem in allMediaRightItems {
|
mediaRightsLoop: for possibleMediaItem in allMediaRightItems {
|
||||||
if !self.allowedMediaRights.contains(possibleMediaItem) {
|
if !self.allowedMediaRights.contains(possibleMediaItem) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaItemTitle: String
|
let mediaItemTitle: String
|
||||||
switch possibleMediaItem {
|
switch possibleMediaItem {
|
||||||
case .photos:
|
case .photos:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPhoto
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPhoto
|
||||||
case .videos:
|
case .videos:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideo
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideo
|
||||||
case .stickersAndGifs:
|
case .stickersAndGifs:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendStickersAndGifs
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendStickersAndGifs
|
||||||
case .music:
|
case .music:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendMusic
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendMusic
|
||||||
case .files:
|
case .files:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendFile
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendFile
|
||||||
case .voiceMessages:
|
case .voiceMessages:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVoiceMessage
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVoiceMessage
|
||||||
case .videoMessages:
|
case .videoMessages:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideoMessage
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideoMessage
|
||||||
case .links:
|
case .links:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionEmbedLinks
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionEmbedLinks
|
||||||
case .polls:
|
case .polls:
|
||||||
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPolls
|
mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPolls
|
||||||
default:
|
default:
|
||||||
continue mediaRightsLoop
|
continue mediaRightsLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaItems.append(AnyComponentWithIdentity(id: possibleMediaItem, component: AnyComponent(ListActionItemComponent(
|
mediaItems.append(AnyComponentWithIdentity(id: possibleMediaItem, component: AnyComponent(ListActionItemComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
title: AnyComponent(VStack([
|
title: AnyComponent(VStack([
|
||||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: mediaItemTitle,
|
string: mediaItemTitle,
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
textColor: environment.theme.list.itemPrimaryTextColor
|
textColor: environment.theme.list.itemPrimaryTextColor
|
||||||
)),
|
)),
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
))),
|
))),
|
||||||
], alignment: .left, spacing: 2.0)),
|
], alignment: .left, spacing: 2.0)),
|
||||||
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
|
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
|
||||||
isSelected: self.mediaRights.contains(possibleMediaItem),
|
isSelected: self.mediaRights.contains(possibleMediaItem),
|
||||||
toggle: { [weak self] in
|
toggle: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.mediaRights.contains(possibleMediaItem) {
|
||||||
|
self.mediaRights.remove(possibleMediaItem)
|
||||||
|
} else {
|
||||||
|
self.mediaRights.insert(possibleMediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.35))
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
icon: .none,
|
||||||
|
accessory: .none,
|
||||||
|
action: { [weak self] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1258,31 +1276,16 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.35))
|
self.state?.updated(transition: .spring(duration: 0.35))
|
||||||
}
|
},
|
||||||
)),
|
highlighting: .disabled
|
||||||
icon: .none,
|
))))
|
||||||
accessory: .none,
|
}
|
||||||
action: { [weak self] _ in
|
configSectionItems.append(AnyComponentWithIdentity(id: "media-sub", component: AnyComponent(ListSubSectionComponent(
|
||||||
guard let self else {
|
theme: environment.theme,
|
||||||
return
|
leftInset: 0.0,
|
||||||
}
|
items: mediaItems
|
||||||
|
|
||||||
if self.mediaRights.contains(possibleMediaItem) {
|
|
||||||
self.mediaRights.remove(possibleMediaItem)
|
|
||||||
} else {
|
|
||||||
self.mediaRights.insert(possibleMediaItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.35))
|
|
||||||
},
|
|
||||||
highlighting: .disabled
|
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
configSectionItems.append(AnyComponentWithIdentity(id: "media-sub", component: AnyComponent(ListSubSectionComponent(
|
|
||||||
theme: environment.theme,
|
|
||||||
leftInset: 0.0,
|
|
||||||
items: mediaItems
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1299,7 +1299,9 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
|
|||||||
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
|
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
|
||||||
let rawText: String
|
let rawText: String
|
||||||
|
|
||||||
if self.isPremiumDisabled {
|
if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||||
|
rawText = interfaceState.strings.Chat_EmptyStateMonoforumPaid_Text(peerTitle, " $ \(starsString)").string
|
||||||
|
} else if self.isPremiumDisabled {
|
||||||
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
|
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
|
||||||
} else {
|
} else {
|
||||||
rawText = interfaceState.strings.Chat_EmptyStatePaidMessaging_Text(peerTitle, " $ \(starsString)").string
|
rawText = interfaceState.strings.Chat_EmptyStatePaidMessaging_Text(peerTitle, " $ \(starsString)").string
|
||||||
@ -1867,7 +1869,11 @@ public final class ChatEmptyNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let channel = peer as? TelegramChannel, channel.isMonoForum {
|
} else if let channel = peer as? TelegramChannel, channel.isMonoForum {
|
||||||
contentType = .starsRequired(interfaceState.sendPaidMessageStars?.value)
|
if let mainChannel = interfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
|
||||||
|
contentType = .regular
|
||||||
|
} else {
|
||||||
|
contentType = .starsRequired(interfaceState.sendPaidMessageStars?.value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
contentType = .regular
|
contentType = .regular
|
||||||
}
|
}
|
||||||
|
@ -864,15 +864,19 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
if replyThreadMessage.peerId != item.context.account.peerId {
|
if replyThreadMessage.peerId != item.context.account.peerId {
|
||||||
if replyThreadMessage.peerId.isGroupOrChannel && item.message.author != nil {
|
if replyThreadMessage.peerId.isGroupOrChannel && item.message.author != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
var isMonoforum = false
|
||||||
isBroadcastChannel = true
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel {
|
||||||
|
if case .broadcast = peer.info {
|
||||||
|
isBroadcastChannel = true
|
||||||
|
}
|
||||||
|
isMonoforum = peer.isMonoForum
|
||||||
}
|
}
|
||||||
|
|
||||||
if replyThreadMessage.isChannelPost, replyThreadMessage.effectiveTopId == item.message.id {
|
if replyThreadMessage.isChannelPost, replyThreadMessage.effectiveTopId == item.message.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isBroadcastChannel {
|
if !isBroadcastChannel && !isMonoforum {
|
||||||
hasAvatar = true
|
hasAvatar = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,9 @@ private final class ChatMessageDateSectionSeparatorNode: ASDisplayNode {
|
|||||||
private let controllerInteraction: ChatControllerInteraction?
|
private let controllerInteraction: ChatControllerInteraction?
|
||||||
private let presentationData: ChatPresentationData
|
private let presentationData: ChatPresentationData
|
||||||
|
|
||||||
private let backgroundNode: NavigationBackgroundNode
|
public let backgroundNode: NavigationBackgroundNode
|
||||||
|
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||||
|
|
||||||
private let patternLayer: SimpleShapeLayer
|
private let patternLayer: SimpleShapeLayer
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -172,6 +174,11 @@ private final class ChatMessageDateSectionSeparatorNode: ASDisplayNode {
|
|||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
if controllerInteraction?.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true, let backgroundContent = controllerInteraction?.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||||
|
backgroundContent.clipsToBounds = true
|
||||||
|
self.backgroundContent = backgroundContent
|
||||||
|
}
|
||||||
|
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: .clear)
|
self.backgroundNode = NavigationBackgroundNode(color: .clear)
|
||||||
self.backgroundNode.isUserInteractionEnabled = false
|
self.backgroundNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
@ -182,7 +189,13 @@ private final class ChatMessageDateSectionSeparatorNode: ASDisplayNode {
|
|||||||
self.backgroundColor = nil
|
self.backgroundColor = nil
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
if let backgroundContent = self.backgroundContent {
|
||||||
|
self.addSubnode(backgroundContent)
|
||||||
|
backgroundContent.layer.mask = self.patternLayer
|
||||||
|
} else {
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.backgroundNode.layer.mask = self.patternLayer
|
||||||
|
}
|
||||||
|
|
||||||
let fullTranslucency: Bool = self.controllerInteraction?.enableFullTranslucency ?? true
|
let fullTranslucency: Bool = self.controllerInteraction?.enableFullTranslucency ?? true
|
||||||
|
|
||||||
@ -196,8 +209,6 @@ private final class ChatMessageDateSectionSeparatorNode: ASDisplayNode {
|
|||||||
linePath.addLine(to: CGPoint(x: 10000.0, y: self.patternLayer.lineWidth * 0.5))
|
linePath.addLine(to: CGPoint(x: 10000.0, y: self.patternLayer.lineWidth * 0.5))
|
||||||
self.patternLayer.path = linePath
|
self.patternLayer.path = linePath
|
||||||
self.patternLayer.lineDashPattern = [6.0 as NSNumber, 2.0 as NSNumber] as [NSNumber]
|
self.patternLayer.lineDashPattern = [6.0 as NSNumber, 2.0 as NSNumber] as [NSNumber]
|
||||||
|
|
||||||
self.backgroundNode.layer.mask = self.patternLayer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
@ -206,6 +217,21 @@ private final class ChatMessageDateSectionSeparatorNode: ASDisplayNode {
|
|||||||
self.backgroundNode.update(size: backgroundFrame.size, transition: transition)
|
self.backgroundNode.update(size: backgroundFrame.size, transition: transition)
|
||||||
|
|
||||||
transition.updateFrame(layer: self.patternLayer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 1.66)))
|
transition.updateFrame(layer: self.patternLayer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 1.66)))
|
||||||
|
|
||||||
|
if let backgroundContent = self.backgroundContent {
|
||||||
|
backgroundContent.allowsGroupOpacity = true
|
||||||
|
self.backgroundNode.isHidden = true
|
||||||
|
|
||||||
|
transition.updateFrame(node: backgroundContent, frame: self.backgroundNode.frame)
|
||||||
|
backgroundContent.cornerRadius = backgroundFrame.size.height / 2.0
|
||||||
|
|
||||||
|
/*if let (rect, containerSize) = self.absolutePosition {
|
||||||
|
var backgroundFrame = backgroundContent.frame
|
||||||
|
backgroundFrame.origin.x += rect.minX
|
||||||
|
backgroundFrame.origin.y += containerSize.height - rect.minY
|
||||||
|
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,15 +485,19 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
if replyThreadMessage.peerId != item.context.account.peerId {
|
if replyThreadMessage.peerId != item.context.account.peerId {
|
||||||
if replyThreadMessage.peerId.isGroupOrChannel && item.message.author != nil {
|
if replyThreadMessage.peerId.isGroupOrChannel && item.message.author != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
var isMonoforum = false
|
||||||
isBroadcastChannel = true
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel {
|
||||||
|
if case .broadcast = peer.info {
|
||||||
|
isBroadcastChannel = true
|
||||||
|
}
|
||||||
|
isMonoforum = peer.isMonoForum
|
||||||
}
|
}
|
||||||
|
|
||||||
if replyThreadMessage.isChannelPost, replyThreadMessage.effectiveTopId == item.message.id {
|
if replyThreadMessage.isChannelPost, replyThreadMessage.effectiveTopId == item.message.id {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isBroadcastChannel {
|
if !isBroadcastChannel && !isMonoforum {
|
||||||
hasAvatar = true
|
hasAvatar = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1891,11 +1891,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let canViewStats: Bool
|
var canViewStats = false
|
||||||
if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden {
|
var canViewAuthor = false
|
||||||
|
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||||
|
if message.effectivelyIncoming(context.account.peerId) {
|
||||||
|
canViewAuthor = true
|
||||||
|
}
|
||||||
|
} else if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden {
|
||||||
canViewStats = canViewReadStats(message: message, participantCount: infoSummaryData.participantCount, isMessageRead: isMessageRead, isPremium: isPremium, appConfig: appConfig)
|
canViewStats = canViewReadStats(message: message, participantCount: infoSummaryData.participantCount, isMessageRead: isMessageRead, isPremium: isPremium, appConfig: appConfig)
|
||||||
} else {
|
|
||||||
canViewStats = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var reactionCount = 0
|
var reactionCount = 0
|
||||||
@ -1922,6 +1925,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: false, isEdit: true, stats: MessageReadStats(reactionCount: 0, peers: [], readTimestamps: [:]), action: nil), false), at: 0)
|
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: false, isEdit: true, stats: MessageReadStats(reactionCount: 0, peers: [], readTimestamps: [:]), action: nil), false), at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if canViewAuthor {
|
||||||
|
actions.insert(.custom(ChatMessageAuthorContextItem(context: context, message: message, action: { c, f, peer in
|
||||||
|
c.dismiss(completion: {
|
||||||
|
controllerInteraction.openPeer(peer, .default, nil, .default)
|
||||||
|
})
|
||||||
|
}), false), at: 0)
|
||||||
|
}
|
||||||
if let peer = message.peers[message.id.peerId], (canViewStats || reactionCount != 0) {
|
if let peer = message.peers[message.id.peerId], (canViewStats || reactionCount != 0) {
|
||||||
var hasReadReports = false
|
var hasReadReports = false
|
||||||
if let channel = peer as? TelegramChannel {
|
if let channel = peer as? TelegramChannel {
|
||||||
@ -2688,6 +2698,313 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class ChatMessageAuthorContextItem: ContextMenuCustomItem {
|
||||||
|
fileprivate let context: AccountContext
|
||||||
|
fileprivate let message: Message
|
||||||
|
fileprivate let action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, EnginePeer) -> Void)?
|
||||||
|
|
||||||
|
init(context: AccountContext, message: Message, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, EnginePeer) -> Void)?) {
|
||||||
|
self.context = context
|
||||||
|
self.message = message
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return ChatMessageAuthorContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol {
|
||||||
|
private let item: ChatMessageAuthorContextItem
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextControllerProtocol?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
private let placeholderCalculationTextNode: ImmediateTextNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
private let shimmerNode: ShimmerEffectNode
|
||||||
|
|
||||||
|
/*private let avatarsNode: AnimatedAvatarSetNode
|
||||||
|
private let avatarsContext: AnimatedAvatarSetContext
|
||||||
|
|
||||||
|
private let placeholderAvatarsNode: AnimatedAvatarSetNode
|
||||||
|
private let placeholderAvatarsContext: AnimatedAvatarSetContext*/
|
||||||
|
|
||||||
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
|
|
||||||
|
private var pointerInteraction: PointerInteraction?
|
||||||
|
|
||||||
|
private var disposable: Disposable?
|
||||||
|
private var peer: EnginePeer?
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: ChatMessageAuthorContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isAccessibilityElement = false
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
|
self.highlightedBackgroundNode.isAccessibilityElement = false
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.placeholderCalculationTextNode = ImmediateTextNode()
|
||||||
|
self.placeholderCalculationTextNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ContextMenuSeen(11), font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
self.placeholderCalculationTextNode.maximumNumberOfLines = 1
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode.isAccessibilityElement = false
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
self.textNode.maximumNumberOfLines = 1
|
||||||
|
self.textNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.shimmerNode = ShimmerEffectNode()
|
||||||
|
self.shimmerNode.clipsToBounds = true
|
||||||
|
|
||||||
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
self.buttonNode.isAccessibilityElement = true
|
||||||
|
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
|
||||||
|
|
||||||
|
/*self.avatarsNode = AnimatedAvatarSetNode()
|
||||||
|
self.avatarsContext = AnimatedAvatarSetContext()
|
||||||
|
|
||||||
|
self.placeholderAvatarsNode = AnimatedAvatarSetNode()
|
||||||
|
self.placeholderAvatarsContext = AnimatedAvatarSetContext()*/
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
|
self.addSubnode(self.shimmerNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
/*self.addSubnode(self.avatarsNode)
|
||||||
|
self.addSubnode(self.placeholderAvatarsNode)*/
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.buttonNode.highligthedChanged = { [weak self] highligted in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if highligted {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
|
self.buttonNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.disposable = (item.context.engine.messages.requestMessageAuthor(id: item.message.id)
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] value in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let value {
|
||||||
|
self.updatePeer(peer: value, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.75
|
||||||
|
}
|
||||||
|
}, willExit: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private var validLayout: (calculatedWidth: CGFloat, size: CGSize)?
|
||||||
|
|
||||||
|
func updatePeer(peer: EnginePeer, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.buttonNode.isUserInteractionEnabled = true
|
||||||
|
|
||||||
|
guard let (calculatedWidth, size) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.peer = peer
|
||||||
|
|
||||||
|
let (_, apply) = self.updateLayout(constrainedWidth: calculatedWidth, constrainedHeight: size.height)
|
||||||
|
apply(size, transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||||
|
let sideInset: CGFloat = 14.0
|
||||||
|
let verticalInset: CGFloat
|
||||||
|
let rightTextInset: CGFloat
|
||||||
|
//let avatarsWidth: CGFloat = 32.0
|
||||||
|
let avatarsWidth: CGFloat = 0
|
||||||
|
|
||||||
|
verticalInset = 12.0
|
||||||
|
rightTextInset = sideInset + 36.0
|
||||||
|
|
||||||
|
let calculatedWidth = min(constrainedWidth, 250.0)
|
||||||
|
|
||||||
|
let textFont = Font.regular(floor(13.0 * (self.presentationData.listsFontSize.baseDisplaySize / 17.0)))
|
||||||
|
let boldTextFont = Font.semibold(floor(13.0 * (self.presentationData.listsFontSize.baseDisplaySize / 17.0)))
|
||||||
|
|
||||||
|
let animatePositions = true
|
||||||
|
|
||||||
|
if let peer = self.peer {
|
||||||
|
let peerTitle = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||||
|
let rawString = self.presentationData.strings.Chat_ContextMenu_AuthorInfo(peerTitle)
|
||||||
|
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: rawString.string, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor))
|
||||||
|
for range in rawString.ranges {
|
||||||
|
string.addAttribute(.foregroundColor, value: self.presentationData.theme.list.itemAccentColor, range: range.range)
|
||||||
|
string.addAttribute(.font, value: boldTextFont, range: range.range)
|
||||||
|
}
|
||||||
|
self.textNode.attributedText = string
|
||||||
|
} else {
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textSize = self.textNode.updateLayout(CGSize(width: calculatedWidth - sideInset - rightTextInset - avatarsWidth - 4.0, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
let placeholderTextSize = self.placeholderCalculationTextNode.updateLayout(CGSize(width: calculatedWidth - sideInset - rightTextInset - avatarsWidth - 4.0, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
let combinedTextHeight = textSize.height
|
||||||
|
return (CGSize(width: calculatedWidth, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
|
||||||
|
self.validLayout = (calculatedWidth: calculatedWidth, size: size)
|
||||||
|
|
||||||
|
let positionTransition: ContainedViewLayoutTransition = animatePositions ? transition : .immediate
|
||||||
|
|
||||||
|
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: sideInset + avatarsWidth + 2.0, y: verticalOrigin), size: textSize)
|
||||||
|
|
||||||
|
positionTransition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||||
|
transition.updateAlpha(node: self.textNode, alpha: self.peer == nil ? 0.0 : 1.0)
|
||||||
|
|
||||||
|
let shimmerHeight: CGFloat = 8.0
|
||||||
|
|
||||||
|
self.shimmerNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: floor((size.height - shimmerHeight) / 2.0)), size: CGSize(width: placeholderTextSize.width, height: shimmerHeight))
|
||||||
|
self.shimmerNode.cornerRadius = shimmerHeight / 2.0
|
||||||
|
let shimmeringForegroundColor: UIColor
|
||||||
|
let shimmeringColor: UIColor
|
||||||
|
if self.presentationData.theme.overallDarkAppearance {
|
||||||
|
let backgroundColor = self.presentationData.theme.contextMenu.backgroundColor.blitOver(self.presentationData.theme.list.plainBackgroundColor, alpha: 1.0)
|
||||||
|
|
||||||
|
shimmeringForegroundColor = self.presentationData.theme.contextMenu.primaryColor.blitOver(backgroundColor, alpha: 0.1)
|
||||||
|
shimmeringColor = self.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3)
|
||||||
|
} else {
|
||||||
|
let backgroundColor = self.presentationData.theme.contextMenu.backgroundColor.blitOver(self.presentationData.theme.list.plainBackgroundColor, alpha: 1.0)
|
||||||
|
|
||||||
|
shimmeringForegroundColor = self.presentationData.theme.contextMenu.primaryColor.blitOver(backgroundColor, alpha: 0.15)
|
||||||
|
shimmeringColor = self.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3)
|
||||||
|
}
|
||||||
|
self.shimmerNode.update(backgroundColor: self.presentationData.theme.list.plainBackgroundColor, foregroundColor: shimmeringForegroundColor, shimmeringColor: shimmeringColor, shapes: [.rect(rect: self.shimmerNode.bounds)], horizontal: true, size: self.shimmerNode.bounds.size)
|
||||||
|
self.shimmerNode.updateAbsoluteRect(self.shimmerNode.frame, within: size)
|
||||||
|
transition.updateAlpha(node: self.shimmerNode, alpha: self.peer == nil ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
/*let avatarsContent: AnimatedAvatarSetContext.Content
|
||||||
|
let placeholderAvatarsContent: AnimatedAvatarSetContext.Content
|
||||||
|
|
||||||
|
var avatarsPeers: [EnginePeer] = []
|
||||||
|
if let peer = self.peer {
|
||||||
|
avatarsPeers = [peer]
|
||||||
|
}
|
||||||
|
avatarsContent = self.avatarsContext.update(peers: avatarsPeers, animated: false)
|
||||||
|
|
||||||
|
placeholderAvatarsContent = self.avatarsContext.updatePlaceholder(color: shimmeringForegroundColor, count: 1, animated: false)
|
||||||
|
|
||||||
|
let avatarsSize = self.avatarsNode.update(context: self.item.context, content: avatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
|
||||||
|
self.avatarsNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize)
|
||||||
|
transition.updateAlpha(node: self.avatarsNode, alpha: self.peer == nil ? 0.0 : 1.0)
|
||||||
|
|
||||||
|
let placeholderAvatarsSize = self.placeholderAvatarsNode.update(context: self.item.context, content: placeholderAvatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
|
||||||
|
self.placeholderAvatarsNode.frame = CGRect(origin: CGPoint(x: self.avatarsNode.frame.minX, y: floor((size.height - placeholderAvatarsSize.height) / 2.0)), size: placeholderAvatarsSize)
|
||||||
|
transition.updateAlpha(node: self.placeholderAvatarsNode, alpha: self.peer == nil ? 1.0 : 0.0)*/
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||||
|
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.performAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var actionTemporarilyDisabled: Bool = false
|
||||||
|
|
||||||
|
func canBeHighlighted() -> Bool {
|
||||||
|
return self.isActionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsHighlighted(isHighlighted: Bool) {
|
||||||
|
self.setIsHighlighted(isHighlighted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAction() {
|
||||||
|
if self.actionTemporarilyDisabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.actionTemporarilyDisabled = true
|
||||||
|
Queue.mainQueue().async { [weak self] in
|
||||||
|
self?.actionTemporarilyDisabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let controller = self.getController() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let peer = self.peer {
|
||||||
|
self.item.action?(controller, { [weak self] result in
|
||||||
|
self?.actionSelected(result)
|
||||||
|
}, peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActionEnabled: Bool {
|
||||||
|
if self.item.action == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return self.peer != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIsHighlighted(_ value: Bool) {
|
||||||
|
if value {
|
||||||
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatReadReportContextItem: ContextMenuCustomItem {
|
final class ChatReadReportContextItem: ContextMenuCustomItem {
|
||||||
fileprivate let context: AccountContext
|
fileprivate let context: AccountContext
|
||||||
fileprivate let message: Message
|
fileprivate let message: Message
|
||||||
|
Loading…
x
Reference in New Issue
Block a user