Merge commit '3f13dea5daa9e27a5e17210235d8ac87004de3fd' into experimental-3

This commit is contained in:
Ilya Laktyushin 2023-01-19 15:35:07 +04:00
commit d9061b0771
15 changed files with 235 additions and 106 deletions

View File

@ -8648,16 +8648,17 @@ Sorry for the inconvenience.";
"RequestPeer.Requirement.Group.HasUsernameOff" = "The group should be private.";
"RequestPeer.Requirement.Group.HasUsernameOn" = "The group should be public.";
"RequestPeer.Requirement.Group.ForumOff" = "The group should have topics off.";
"RequestPeer.Requirement.Group.ForumOn" = "The group should have topics on.";
"RequestPeer.Requirement.Group.ForumOff" = "The group should have topics turned off.";
"RequestPeer.Requirement.Group.ForumOn" = "The group should have topics turned on.";
"RequestPeer.Requirement.Group.ParticipantOn" = "Bot should be in the group.";
"RequestPeer.Requirement.Group.CreatorOn" = "You should be the owner of the group.";
"RequestPeer.Requirement.Group.Rights" = "You have the admin rights to %@.";
"RequestPeer.Requirement.Group.Rights.Info" = "change group info";
"RequestPeer.Requirement.Group.Rights.Send" = "post messages";
"RequestPeer.Requirement.Group.Rights.Delete" = "delete messages";
"RequestPeer.Requirement.Group.Rights.Edit" = "delete messages";
"RequestPeer.Requirement.Group.Rights.Edit" = "edit messages";
"RequestPeer.Requirement.Group.Rights.Ban" = "ban users";
"RequestPeer.Requirement.Group.Rights.Invite" = "invite users via link";
"RequestPeer.Requirement.Group.Rights.Pin" = "pin messages";
@ -8672,10 +8673,10 @@ Sorry for the inconvenience.";
"RequestPeer.Requirement.Channel.CreatorOn" = "You should be the owner of the channel.";
"RequestPeer.Requirement.Channel.Rights" = "You have the admin rights to %@.";
"RequestPeer.Requirement.Channel.Rights.Info" = "change group info";
"RequestPeer.Requirement.Channel.Rights.Info" = "change channel info";
"RequestPeer.Requirement.Channel.Rights.Send" = "post messages";
"RequestPeer.Requirement.Channel.Rights.Delete" = "delete messages";
"RequestPeer.Requirement.Channel.Rights.Edit" = "delete messages";
"RequestPeer.Requirement.Channel.Rights.Edit" = "edit messages";
"RequestPeer.Requirement.Channel.Rights.Ban" = "ban users";
"RequestPeer.Requirement.Channel.Rights.Invite" = "invite users via link";
"RequestPeer.Requirement.Channel.Rights.Pin" = "pin messages";
@ -8697,7 +8698,6 @@ Sorry for the inconvenience.";
"Conversation.ViewInChannel" = "View in Channel";
"Conversation.HoldForAudioOnly" = "Hold to record audio.";
"Conversation.HoldForVideoOnly" = "Hold to record video.";
"Conversation.Translation.TranslateTo" = "Translate to %@";
@ -8708,13 +8708,17 @@ Sorry for the inconvenience.";
"Conversation.Translation.AddedToDoNotTranslateText" = "**%@** is added to the Do Not Translate list.";
"Conversation.Translation.TranslationBarHiddenText" = "Translation bar is now hidden for this channel.";
"ProfilePhoto.SetEmoji" = "Set Emoji";
"AvatarEditor.Background" = "BACKGROUND";
"AvatarEditor.EmojiOrSticker" = "EMOJI OR STICKER";
"AvatarEditor.Emoji" = "EMOJI";
"AvatarEditor.Stickers" = "STICKERS";
"AvatarEditor.Emoji" = "Emoji";
"AvatarEditor.Stickers" = "Stickers";
"AvatarEditor.SwitchToEmoji" = "SWITCH TO EMOJI";
"AvatarEditor.SwitchToStickers" = "SWITCH TO STICKERS";
"AvatarEditor.SetVideo" = "Set Video";
"AvatarEditor.SetProfilePhoto" = "Set as Profile Photo";
"AvatarEditor.SetGroupPhoto" = "Set as Group Photo";
"AvatarEditor.SetChannelPhoto" = "Set as Group Photo";
"AvatarEditor.Set" = "Set";

View File

@ -194,7 +194,7 @@
[itemViews addObject:galleryItem];
if (!_signup) {
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:@"Emoji or Sticker" type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SetEmoji") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
{
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)

View File

@ -23,6 +23,7 @@ struct ParsedDialogs {
let topMessageIds: [PeerId: MessageId]
let storeMessages: [StoreMessage]
let ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout]
let hiddenTranslations: [PeerId: Bool]
let lowerNonPinnedIndex: MessageIndex?
let referencedFolders: [PeerGroupId: PeerGroupUnreadCountersSummary]
@ -55,6 +56,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
var channelStates: [PeerId: Int32] = [:]
var topMessageIds: [PeerId: MessageId] = [:]
var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:]
var hiddenTranslations: [PeerId: Bool] = [:]
var storeMessages: [StoreMessage] = []
var nonPinnedDialogsTopMessageIds = Set<MessageId>()
@ -112,6 +114,10 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
ttlPeriods[peer.peerId] = .known(ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
if (flags & (1 << 6)) != 0 {
hiddenTranslations[peer.peerId] = true
}
let isPinned = (flags & (1 << 2)) != 0
if !isPinned {
nonPinnedDialogsTopMessageIds.insert(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: topMessage))
@ -190,6 +196,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
topMessageIds: topMessageIds,
storeMessages: storeMessages,
ttlPeriods: ttlPeriods,
hiddenTranslations: hiddenTranslations,
lowerNonPinnedIndex: lowerNonPinnedIndex,
referencedFolders: referencedFolders
@ -208,6 +215,7 @@ struct FetchedChatList {
var channelStates: [PeerId: Int32]
var storeMessages: [StoreMessage]
var topMessageIds: [PeerId: MessageId]
var hiddenTranslations: [PeerId: Bool]
var lowerNonPinnedIndex: MessageIndex?
@ -316,6 +324,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
var channelStates: [PeerId: Int32] = [:]
var storeMessages: [StoreMessage] = []
var topMessageIds: [PeerId: MessageId] = [:]
var hiddenTranslations: [PeerId: Bool] = [:]
peers.append(contentsOf: parsedRemoteChats.peers)
peerPresences.merge(parsedRemoteChats.peerPresences, uniquingKeysWith: { _, updated in updated })
@ -327,6 +336,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
channelStates.merge(parsedRemoteChats.channelStates, uniquingKeysWith: { _, updated in updated })
storeMessages.append(contentsOf: parsedRemoteChats.storeMessages)
topMessageIds.merge(parsedRemoteChats.topMessageIds, uniquingKeysWith: { _, updated in updated })
hiddenTranslations.merge(parsedRemoteChats.hiddenTranslations, uniquingKeysWith: { _, updated in updated })
if let parsedPinnedChats = parsedPinnedChats {
peers.append(contentsOf: parsedPinnedChats.peers)
@ -339,6 +349,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
channelStates.merge(parsedPinnedChats.channelStates, uniquingKeysWith: { _, updated in updated })
storeMessages.append(contentsOf: parsedPinnedChats.storeMessages)
topMessageIds.merge(parsedPinnedChats.topMessageIds, uniquingKeysWith: { _, updated in updated })
hiddenTranslations.merge(parsedPinnedChats.hiddenTranslations, uniquingKeysWith: { _, updated in updated })
}
var peerGroupIds: [PeerId: PeerGroupId] = [:]
@ -362,6 +373,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
reactionTagSummaries.merge(folderChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated })
channelStates.merge(folderChats.channelStates, uniquingKeysWith: { _, updated in updated })
storeMessages.append(contentsOf: folderChats.storeMessages)
hiddenTranslations.merge(folderChats.hiddenTranslations, uniquingKeysWith: { _, updated in updated })
}
var pinnedItemIds: [PeerId]?
@ -398,6 +410,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
channelStates: channelStates,
storeMessages: storeMessages,
topMessageIds: topMessageIds,
hiddenTranslations: hiddenTranslations,
lowerNonPinnedIndex: parsedRemoteChats.lowerNonPinnedIndex,

View File

@ -835,6 +835,19 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
})
}
for (peerId, _) in fetchedChats.hiddenTranslations {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if peerId.namespace == Namespaces.Peer.CloudChannel {
let current = (current as? CachedChannelData) ?? CachedChannelData()
var updatedFlags = current.flags
updatedFlags.insert(.translationHidden)
return current.withUpdatedFlags(updatedFlags)
} else {
return current
}
})
}
transaction.replaceChatListHole(groupId: groupId, index: hole.index, hole: fetchedChats.lowerNonPinnedIndex.flatMap(ChatListHole.init))
for peerId in fetchedChats.chatPeerIds {

View File

@ -19,6 +19,7 @@ public struct CachedChannelFlags: OptionSet {
public static let canChangePeerGeoLocation = CachedChannelFlags(rawValue: 1 << 5)
public static let canDeleteHistory = CachedChannelFlags(rawValue: 1 << 6)
public static let antiSpamEnabled = CachedChannelFlags(rawValue: 1 << 7)
public static let translationHidden = CachedChannelFlags(rawValue: 1 << 8)
}
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {

View File

@ -77,6 +77,19 @@ func _internal_translateMessages(account: Account, messageIds: [EngineMessage.Id
func _internal_togglePeerMessagesTranslationHidden(account: Account, peerId: EnginePeer.Id, hidden: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData {
var updatedFlags = cachedData.flags
if hidden {
updatedFlags.insert(.translationHidden)
} else {
updatedFlags.remove(.translationHidden)
}
return cachedData.withUpdatedFlags(updatedFlags)
} else {
return cachedData
}
})
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in

View File

@ -375,7 +375,56 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
return .generic
}
|> mapToSignal { photo -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if peer.id != accountPeerId {
if peer.id == accountPeerId {
var updatedImage: TelegramMediaImage?
var representations: [TelegramMediaImageRepresentation] = []
switch photo {
case let .photo(apiPhoto, _):
updatedImage = telegramMediaImageFromApiPhoto(apiPhoto)
switch apiPhoto {
case .photoEmpty:
break
case let .photo(_, id, _, _, _, sizes, _, dcId):
var sizes = sizes
if sizes.count == 3 {
sizes.remove(at: 1)
}
for size in sizes {
switch size {
case let .photoSize(_, w, h, _):
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: id, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: nil, localId: nil), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
case let .photoSizeProgressive(_, w, h, sizes):
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: id, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: nil, localId: nil), progressiveSizes: sizes, immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
default:
break
}
}
}
}
return postbox.transaction { transaction -> UpdatePeerPhotoStatus in
if let peer = transaction.getPeer(peer.id) {
updatePeers(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in
if let peer = peer as? TelegramUser {
if customPeerPhotoMode == .suggest || fallback {
return peer
} else {
return peer.withUpdatedPhoto(representations)
}
} else {
return peer
}
})
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedPersonalPhoto(.known(updatedImage))
} else {
return nil
}
}
}
return .complete([])
} |> mapError { _ -> UploadPeerPhotoError in }
} else {
var updatedUsers: [TelegramUser] = []
switch photo {
case let .photo(_, apiUsers):
@ -405,7 +454,6 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
return .complete([])
} |> mapError { _ -> UploadPeerPhotoError in }
}
return .single(.complete([]))
}
} else {
let request: Signal<Api.Updates, MTRpcError>

View File

@ -469,6 +469,9 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if (flags2 & Int32(1 << 1)) != 0 {
channelFlags.insert(.antiSpamEnabled)
}
if (flags2 & Int32(1 << 3)) != 0 {
channelFlags.insert(.translationHidden)
}
let sendAsPeerId = defaultSendAs?.peerId

View File

@ -74,17 +74,20 @@ final class AvatarEditorScreenComponent: Component {
let context: AccountContext
let ready: Promise<Bool>
let peerType: AvatarEditorScreen.PeerType
let initialFileId: Int64?
let initialBackgroundColors: [Int32]?
init(
context: AccountContext,
ready: Promise<Bool>,
peerType: AvatarEditorScreen.PeerType,
initialFileId: Int64?,
initialBackgroundColors: [Int32]?
) {
self.context = context
self.ready = ready
self.peerType = peerType
self.initialFileId = initialFileId
self.initialBackgroundColors = initialBackgroundColors
}
@ -93,6 +96,9 @@ final class AvatarEditorScreenComponent: Component {
if lhs.context !== rhs.context {
return false
}
if lhs.peerType != rhs.peerType {
return false
}
if lhs.initialFileId != rhs.initialFileId {
return false
}
@ -241,6 +247,7 @@ final class AvatarEditorScreenComponent: Component {
}
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if query.isEmpty {
strongSelf.emojiSearchDisposable.set(nil)
@ -351,7 +358,7 @@ final class AvatarEditorScreenComponent: Component {
EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
groupId: "emoji",
title: "Emoji",
title: presentationData.strings.AvatarEditor_Emoji,
subtitle: nil,
actionButtonTitle: nil,
isFeatured: false,
@ -370,7 +377,7 @@ final class AvatarEditorScreenComponent: Component {
EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
groupId: "stickers",
title: "Stickers",
title: presentationData.strings.AvatarEditor_Stickers,
subtitle: nil,
actionButtonTitle: nil,
isFeatured: false,
@ -1136,11 +1143,21 @@ final class AvatarEditorScreenComponent: Component {
contentHeight += 16.0
}
let buttonText: String
switch component.peerType {
case .user:
buttonText = strings.AvatarEditor_SetProfilePhoto
case .group:
buttonText = strings.AvatarEditor_SetGroupPhoto
case .channel:
buttonText = strings.AvatarEditor_SetChannelPhoto
}
let buttonSize = self.buttonView.update(
transition: transition,
component: AnyComponent(
SolidRoundedButtonComponent(
title: strings.AvatarEditor_SetVideo,
title: buttonText,
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
fontSize: 17.0,
height: 50.0,
@ -1179,7 +1196,7 @@ final class AvatarEditorScreenComponent: Component {
entity.scale = 3.3
var documentId: Int64 = 0
if case let .file(file) = entity.content, !file.isCustomEmoji {
if case let .file(file) = entity.content, file.isCustomEmoji {
documentId = file.fileId.id
}
@ -1192,7 +1209,7 @@ final class AvatarEditorScreenComponent: Component {
entitiesData: entitiesData,
image: nil,
stillImage: nil,
hasAnimation: true,
hasAnimation: entity.isAnimated,
stickers: []
)
@ -1238,6 +1255,11 @@ final class AvatarEditorScreenComponent: Component {
}
public final class AvatarEditorScreen: ViewControllerComponentContainer {
public enum PeerType {
case user
case group
case channel
}
fileprivate let context: AccountContext
private let readyValue = Promise<Bool>()
@ -1247,16 +1269,18 @@ public final class AvatarEditorScreen: ViewControllerComponentContainer {
public var completion: (UIImage, URL, TGVideoEditAdjustments, @escaping () -> Void) -> Void = { _, _, _, _ in }
public init(context: AccountContext, initialFileId: Int64?, initialBackgroundColors: [Int32]?) {
public init(context: AccountContext, peerType: PeerType, initialFileId: Int64?, initialBackgroundColors: [Int32]?) {
self.context = context
let componentReady = Promise<Bool>()
super.init(context: context, component: AvatarEditorScreenComponent(context: context, ready: componentReady, initialFileId: initialFileId, initialBackgroundColors: initialBackgroundColors), navigationBarAppearance: .transparent)
super.init(context: context, component: AvatarEditorScreenComponent(context: context, ready: componentReady, peerType: peerType, initialFileId: initialFileId, initialBackgroundColors: initialBackgroundColors), navigationBarAppearance: .transparent)
self.navigationPresentation = .modal
self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true)))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
}
required public init(coder aDecoder: NSCoder) {

View File

@ -202,7 +202,7 @@ final class AvatarPreviewComponent: Component {
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
let source = AnimatedStickerResourceSource(account: component.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .count(2), mode: .direct(cachePathPrefix: nil))
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .count(1), mode: .direct(cachePathPrefix: nil))
self.animationNode?.visibility = true
self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384)

View File

@ -6699,11 +6699,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if peerId.namespace == Namespaces.Peer.CloudChannel {
self.translationStateDisposable = (chatTranslationState(context: self.context, peerId: peerId)
|> deliverOnMainQueue).start(next: { [weak self] translationState in
self.translationStateDisposable = combineLatest(
queue: .mainQueue(),
chatTranslationState(context: self.context, peerId: peerId),
self.chatDisplayNode.historyNode.cachedPeerDataAndMessages
).start(next: { [weak self] translationState, cachedDataAndMessages in
if let strongSelf = self {
let (cachedData, _) = cachedDataAndMessages
var isHidden = false
if let cachedData = cachedData as? CachedChannelData, cachedData.flags.contains(.translationHidden) {
isHidden = true
}
var chatTranslationState: ChatPresentationTranslationState?
if let translationState, translationState.isHidden != true && !translationState.fromLang.isEmpty {
if let translationState, !isHidden && !translationState.fromLang.isEmpty {
chatTranslationState = ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? strongSelf.presentationData.strings.baseLanguageCode)
}
strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in
@ -10055,16 +10063,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let context = strongSelf.context
let _ = updateChatTranslationStateInteractively(engine: context.engine, peerId: peerId, { current in
return current?.withIsHidden(true)
}).start()
let _ = context.engine.messages.togglePeerMessagesTranslationHidden(peerId: peerId, hidden: true).start()
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/Translate"), color: .white)!, title: nil, text: presentationData.strings.Conversation_Translation_TranslationBarHiddenText, round: false, undoText: presentationData.strings.Undo_Undo), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .undo = action {
let _ = updateChatTranslationStateInteractively(engine: context.engine, peerId: peerId, { current in
return current?.withIsHidden(false)
}).start()
let _ = context.engine.messages.togglePeerMessagesTranslationHidden(peerId: peerId, hidden: false).start()
}
return true
}), in: .current)

View File

@ -444,31 +444,33 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
let copyTextWithEntities = {
var messageEntities: [MessageTextEntity]?
var restrictedText: String?
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
messageEntities = attribute.entities
}
if let attribute = attribute as? RestrictedContentMessageAttribute {
restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? ""
}
var messageEntities: [MessageTextEntity]?
var restrictedText: String?
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
messageEntities = attribute.entities
}
if let attribute = attribute as? RestrictedContentMessageAttribute {
restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? ""
}
}
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
storeMessageTextInPasteboard(translation.text, entities: translation.entities)
} else {
storeMessageTextInPasteboard(message.text, entities: messageEntities)
}
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied)
controllerInteraction.displayUndo(content)
})
}
copyTextWithEntities()
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied)
controllerInteraction.displayUndo(content)
})
f(.default)
})))
}
@ -1093,7 +1095,12 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
storeMessageTextInPasteboard(messageText, entities: messageEntities)
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
storeMessageTextInPasteboard(translation.text, entities: translation.entities)
} else {
storeMessageTextInPasteboard(messageText, entities: messageEntities)
}
}
Queue.mainQueue().after(0.2, {
@ -1160,7 +1167,12 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSpeak, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.performTextSelectionAction(!isCopyProtected, NSAttributedString(string: messageText), .speak)
var text = messageText
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
text = translation.text
}
controllerInteraction.performTextSelectionAction(!isCopyProtected, NSAttributedString(string: text), .speak)
f(.default)
})))
}

View File

@ -4849,31 +4849,37 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
items.insert(.separator, at: itemsCount)
}
} else if let channel = peer as? TelegramChannel {
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_Stats, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openStats()
})))
}
if let _ = strongSelf.translationState {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let strongSelf = self {
Queue.mainQueue().after(0.3, {
let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, { state in
return state?.withIsHidden(false)
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData {
if cachedData.flags.contains(.canViewStats) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_Stats, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.openStats()
})))
}
if cachedData.flags.contains(.translationHidden) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let strongSelf = self {
let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, { current in
return current?.withIsEnabled(true)
}).start()
})
self?.openChatForTranslation()
}
})))
Queue.mainQueue().after(0.2, {
let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false)
|> deliverOnMainQueue).start(completed: { [weak self] in
self?.openChatForTranslation()
})
})
}
})))
}
}
var canReport = true
@ -7263,7 +7269,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
guard let strongSelf = self, let completion else {
return
}
let controller = AvatarEditorScreen(context: strongSelf.context, initialFileId: emojiMarkup?.fileId, initialBackgroundColors: emojiMarkup?.backgroundColors)
let peerType: AvatarEditorScreen.PeerType
if case .legacyGroup = peer {
peerType = .group
} else if case let .channel(channel) = peer {
if case .group = channel.info {
peerType = .group
} else {
peerType = .channel
}
} else {
peerType = .user
}
let controller = AvatarEditorScreen(context: strongSelf.context, peerType: peerType, initialFileId: emojiMarkup?.fileId, initialBackgroundColors: emojiMarkup?.backgroundColors)
controller.completion = completion
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
}

View File

@ -1148,6 +1148,9 @@ private func stringForRequestPeerType(strings: PresentationStrings, peerType: Re
append(strings.RequestPeer_Requirement_Group_ForumOff)
}
}
if group.botParticipant {
append(strings.RequestPeer_Requirement_Group_ParticipantOn)
}
if let adminRights = group.userAdminRights, !group.isCreator {
var rights: [String] = []
if adminRights.rights.contains(.canChangeInfo) {

View File

@ -10,33 +10,25 @@ public struct ChatTranslationState: Codable {
enum CodingKeys: String, CodingKey {
case baseLang
case fromLang
case forcedFromLang
case toLang
case isEnabled
case isHidden
}
public let baseLang: String
public let fromLang: String
public let forcedFromLang: String?
public let toLang: String?
public let isEnabled: Bool
public let isHidden: Bool?
public init(
baseLang: String,
fromLang: String,
forcedFromLang: String?,
toLang: String?,
isEnabled: Bool,
isHidden: Bool?
isEnabled: Bool
) {
self.baseLang = baseLang
self.fromLang = fromLang
self.forcedFromLang = forcedFromLang
self.toLang = toLang
self.isEnabled = isEnabled
self.isHidden = isHidden
}
public init(from decoder: Decoder) throws {
@ -44,10 +36,8 @@ public struct ChatTranslationState: Codable {
self.baseLang = try container.decode(String.self, forKey: .baseLang)
self.fromLang = try container.decode(String.self, forKey: .fromLang)
self.forcedFromLang = try container.decodeIfPresent(String.self, forKey: .forcedFromLang)
self.toLang = try container.decodeIfPresent(String.self, forKey: .toLang)
self.isEnabled = try container.decode(Bool.self, forKey: .isEnabled)
self.isHidden = try container.decodeIfPresent(Bool.self, forKey: .isHidden)
}
public func encode(to encoder: Encoder) throws {
@ -55,20 +45,16 @@ public struct ChatTranslationState: Codable {
try container.encode(self.baseLang, forKey: .baseLang)
try container.encode(self.fromLang, forKey: .fromLang)
try container.encodeIfPresent(self.forcedFromLang, forKey: .forcedFromLang)
try container.encodeIfPresent(self.toLang, forKey: .toLang)
try container.encode(self.isEnabled, forKey: .isEnabled)
try container.encodeIfPresent(self.isHidden, forKey: .isHidden)
}
public func withToLang(_ toLang: String?) -> ChatTranslationState {
return ChatTranslationState(
baseLang: self.baseLang,
fromLang: self.fromLang,
forcedFromLang: self.forcedFromLang,
toLang: toLang,
isEnabled: self.isEnabled,
isHidden: self.isHidden
isEnabled: self.isEnabled
)
}
@ -76,21 +62,8 @@ public struct ChatTranslationState: Codable {
return ChatTranslationState(
baseLang: self.baseLang,
fromLang: self.fromLang,
forcedFromLang: self.forcedFromLang,
toLang: self.toLang,
isEnabled: isEnabled,
isHidden: self.isHidden
)
}
public func withIsHidden(_ isHidden: Bool) -> ChatTranslationState {
return ChatTranslationState(
baseLang: self.baseLang,
fromLang: self.fromLang,
forcedFromLang: self.forcedFromLang,
toLang: self.toLang,
isEnabled: self.isEnabled,
isHidden: isHidden
isEnabled: isEnabled
)
}
}
@ -210,7 +183,7 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
}
}
let fromLang = mostFrequent?.0 ?? ""
let state = ChatTranslationState(baseLang: baseLang, fromLang: fromLang, forcedFromLang: nil, toLang: nil, isEnabled: false, isHidden: false)
let state = ChatTranslationState(baseLang: baseLang, fromLang: fromLang, toLang: nil, isEnabled: false)
let _ = updateChatTranslationState(engine: context.engine, peerId: peerId, state: state).start()
return state
}