diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 85eaa12dba..95fafb72bf 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m index b6c3e15132..2761c3bbc8 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m @@ -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) diff --git a/submodules/TelegramCore/Sources/State/FetchChatList.swift b/submodules/TelegramCore/Sources/State/FetchChatList.swift index 4379ac3b1f..b171afcf8f 100644 --- a/submodules/TelegramCore/Sources/State/FetchChatList.swift +++ b/submodules/TelegramCore/Sources/State/FetchChatList.swift @@ -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() @@ -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, diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index e155dfb30e..355764a701 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index 74c5dc1432..345e5aea37 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index e5c08f1c7c..eb2bf05e97 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -77,6 +77,19 @@ func _internal_translateMessages(account: Account, messageIds: [EngineMessage.Id func _internal_togglePeerMessagesTranslationHidden(account: Account, peerId: EnginePeer.Id, hidden: Bool) -> Signal { 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 in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift index 31e96678f9..1357b59f43 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift @@ -375,7 +375,56 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state return .generic } |> mapToSignal { photo -> Signal 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 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index b7e640e23d..fbaffafd3e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -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 diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index df862dfcba..ba48097e27 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -74,17 +74,20 @@ final class AvatarEditorScreenComponent: Component { let context: AccountContext let ready: Promise + let peerType: AvatarEditorScreen.PeerType let initialFileId: Int64? let initialBackgroundColors: [Int32]? init( context: AccountContext, ready: Promise, + 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() @@ -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() - 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) { diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift index d02043b8dd..bb4b3e6f47 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index da02b08c4c..98cb1e4138 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index cd382d2274..3e32ac62f4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -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) }))) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index dc85bd8f76..3a7438e719 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 12cc9148d1..6e7fc1ed8c 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -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) { diff --git a/submodules/TranslateUI/Sources/ChatTranslation.swift b/submodules/TranslateUI/Sources/ChatTranslation.swift index 6f7289a4b2..1b254eae8c 100644 --- a/submodules/TranslateUI/Sources/ChatTranslation.swift +++ b/submodules/TranslateUI/Sources/ChatTranslation.swift @@ -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 }