From b938726fb8f115adac2ff6c836e791117af8b9c4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 9 Feb 2024 01:05:32 +0400 Subject: [PATCH] Group boosts --- .../Telegram-iOS/en.lproj/Localizable.strings | 24 +++++- .../Sources/MediaPickerPlaceholderNode.swift | 4 +- .../Sources/MediaPickerScreen.swift | 3 +- .../Sources/CreateGiveawayController.swift | 10 ++- .../Sources/GiveawayInfoController.swift | 15 ++-- .../Sources/PremiumBoostLevelsScreen.swift | 21 ++++- .../Sources/StickerPreviewPeekContent.swift | 2 +- .../Sources/ServiceMessageStrings.swift | 12 +-- ...ChatMessageGiveawayBubbleContentNode.swift | 6 +- ...hatMessageWallpaperBubbleContentNode.swift | 22 +++--- .../Sources/ChatEntityKeyboardInputNode.swift | 14 ++-- .../Sources/GroupStickerPackCurrentItem.swift | 76 ++++++++++++++----- .../PeerInfoScreen/Sources/PeerInfoData.swift | 49 +++++++++--- .../Sources/PeerInfoScreen.swift | 2 +- .../Sources/ChannelAppearanceScreen.swift | 4 + .../Sources/WallpaperGalleryItem.swift | 9 ++- .../Sources/ShareWithPeersScreen.swift | 53 ++++++++----- .../Sources/ShareWithPeersScreenState.swift | 4 +- .../Sources/AttachmentFileController.swift | 2 +- .../Sources/AttachmentFileEmptyItem.swift | 64 ++++++++++------ .../TelegramUI/Sources/ChatController.swift | 34 +++++++-- .../ChatControllerOpenAttachmentMenu.swift | 15 ++-- .../Sources/ChatInterfaceInputContexts.swift | 8 +- .../Sources/ChatTextInputPanelNode.swift | 14 ++-- 24 files changed, 325 insertions(+), 142 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 144a5b0130..48f4824125 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11101,8 +11101,6 @@ Sorry for the inconvenience."; "Notification.GiveawayStartedGroup" = "%1$@ just started a giveaway of Telegram Premium subscriptions for its members."; -"Chat.Giveaway.Message.Group.Participants" = "All subscribers of this group:"; - "Chat.Giveaway.Info.Group.RandomMembers_1" = "**%@** random member"; "Chat.Giveaway.Info.Group.RandomMembers_any" = "**%@** random members"; @@ -11238,3 +11236,25 @@ Sorry for the inconvenience."; "GroupBoost.Error.PremiumNeededText" = "Only **Telegram Premium** subscribers can boost groups. Do you want to subscribe to **Telegram Premium**?"; "Notification.GroupChangedWallpaper" = "Group set a new wallpaper"; +"Notification.YouChangedGroupWallpaper" = "You set a new wallpaper for this group"; + +"WallpaperPreview.GroupHeader" = "All members will see this wallpaper"; + +"GroupBoost.BoostGroup" = "Boost Group"; + +"Chat.Giveaway.Info.NotAllowedAdminGroup" = "You are not eligible to participate in this giveaway, because you are an admin of participating group (**%@**)."; + +"Chat.Giveaway.Message.Group.Participants" = "All members of this group:"; +"Chat.Giveaway.Message.Group.ParticipantsNew" = "All users who join this group after this date:"; + +"Chat.Giveaway.Message.Group.ParticipantsMany" = "All subscribers of the channels below:"; +"Chat.Giveaway.Message.Group.ParticipantsNewMany" = "All users who join the groups below after this date:"; + +"Story.Privacy.KeepOnGroupPage" = "Post to Group Page"; +"Story.Privacy.KeepOnGroupPageInfo" = "Keep this story on group page even after it expires in %@."; +"Story.Privacy.TooltipStoryArchivedGroup" = "Users will see this story on the group page even after it expires."; + +"BoostGift.Members.Subtitle" = "select up to %@ members"; +"BoostGift.Members.SectionTitle" = "MEMBERS"; +"BoostGift.Members.Search" = "Search Members"; +"BoostGift.Members.MaximumReached" = "You can select maximum %@ members."; diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift index bc086c41ff..3e9f7153f2 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift @@ -11,7 +11,7 @@ import PresentationDataUtils final class MediaPickerPlaceholderNode: ASDisplayNode { enum Content { case intro(story: Bool) - case bannedSendMedia(String) + case bannedSendMedia(text: String, canBoost: Bool) } private let content: Content @@ -147,7 +147,7 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { case let .intro(story): title = strings.Attachment_MediaAccessTitle text = story ? strings.Attachment_MediaAccessStoryText : strings.Attachment_MediaAccessText - case let .bannedSendMedia(banDescription): + case let .bannedSendMedia(banDescription, _): title = "" text = banDescription } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index e63699914f..04eda3be56 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1308,6 +1308,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let (untilDate, personal) = bannedSendMedia { self.gridNode.isHidden = true + self.controller?.titleView.isEnabled = false let banDescription: String if untilDate != 0 && untilDate != Int32.max { @@ -1323,7 +1324,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let current = self.placeholderNode { placeholderNode = current } else { - placeholderNode = MediaPickerPlaceholderNode(content: .bannedSendMedia(banDescription)) + placeholderNode = MediaPickerPlaceholderNode(content: .bannedSendMedia(text: banDescription, canBoost: false)) self.containerNode.insertSubnode(placeholderNode, aboveSubnode: self.gridNode) self.placeholderNode = placeholderNode diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 0e4ddc0885..d07a03aa9e 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -1051,6 +1051,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio } buyActionImpl = { [weak controller] in + let isGroup = isGroupValue.with { $0 } + let state = stateValue.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1131,10 +1133,10 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio switch state.mode { case .giveaway: title = presentationData.strings.BoostGift_GiveawayCreated_Title - text = presentationData.strings.BoostGift_GiveawayCreated_Text + text = isGroup ? presentationData.strings.BoostGift_Group_GiveawayCreated_Text : presentationData.strings.BoostGift_GiveawayCreated_Text case .gift: title = presentationData.strings.BoostGift_PremiumGifted_Title - text = presentationData.strings.BoostGift_PremiumGifted_Text + text = isGroup ? presentationData.strings.BoostGift_Group_PremiumGifted_Text : presentationData.strings.BoostGift_PremiumGifted_Text } let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in @@ -1224,11 +1226,11 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio } openPeersSelectionImpl = { + let isGroup = isGroupValue.with { $0 } let state = stateValue.with { $0 } - let stateContext = ShareWithPeersScreen.StateContext( context: context, - subject: .members(peerId: peerId, searchQuery: nil), + subject: .members(isGroup: isGroup, peerId: peerId, searchQuery: nil), initialPeerIds: Set(state.peers) ) let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in diff --git a/submodules/PremiumUI/Sources/GiveawayInfoController.swift b/submodules/PremiumUI/Sources/GiveawayInfoController.swift index b27fe0dd90..3cc7f0ea2b 100644 --- a/submodules/PremiumUI/Sources/GiveawayInfoController.swift +++ b/submodules/PremiumUI/Sources/GiveawayInfoController.swift @@ -35,15 +35,6 @@ public func presentGiveawayInfoController( let giveaway = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway let giveawayResults = message.media.first(where: { $0 is TelegramMediaGiveawayResults }) as? TelegramMediaGiveawayResults -// var channelPeerId: EnginePeer.Id? -// if let giveaway { -// if let peerId = giveaway.channelPeerIds.first { -// channelPeerId = peerId -// } -// } else if let _ = giveawayResults { -// channelPeerId = message.author?.id -// } - var quantity: Int32 = 0 if let giveaway { quantity = giveaway.quantity @@ -173,10 +164,14 @@ public func presentGiveawayInfoController( participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedJoinedEarly(joinDate).string case let .channelAdmin(adminId): var channelName = peerName + var isGroup = false if let maybePeer = peerMap[adminId], let peer = maybePeer { channelName = peer.compactDisplayTitle + if case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } } - participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedAdmin(channelName).string + participation = isGroup ? presentationData.strings.Chat_Giveaway_Info_NotAllowedAdminGroup(channelName).string : presentationData.strings.Chat_Giveaway_Info_NotAllowedAdmin(channelName).string case .disallowedCountry: participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedCountry } diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 9c1240a0ad..555fd9fcd6 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -1183,10 +1183,14 @@ private final class SheetContent: CombinedComponent { } private final class BoostLevelsContainerComponent: CombinedComponent { + class ExternalState { + var isGroup: Bool = false + } + let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings - + let externalState: ExternalState let peerId: EnginePeer.Id let mode: PremiumBoostLevelsScreen.Mode let status: ChannelBoostStatus? @@ -1202,6 +1206,7 @@ private final class BoostLevelsContainerComponent: CombinedComponent { context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, + externalState: ExternalState, peerId: EnginePeer.Id, mode: PremiumBoostLevelsScreen.Mode, status: ChannelBoostStatus?, @@ -1216,6 +1221,7 @@ private final class BoostLevelsContainerComponent: CombinedComponent { self.context = context self.theme = theme self.strings = strings + self.externalState = externalState self.peerId = peerId self.mode = mode self.status = status @@ -1309,6 +1315,7 @@ private final class BoostLevelsContainerComponent: CombinedComponent { } if let isGroup { + component.externalState.isGroup = isGroup let scroll = scroll.update( component: ScrollComponent( content: AnyComponent( @@ -1556,6 +1563,8 @@ public class PremiumBoostLevelsScreen: ViewController { let footerContainerView: UIView let footerView: ComponentHostView + private let externalState = BoostLevelsContainerComponent.ExternalState() + private(set) var isExpanded = false private var panGestureRecognizer: UIPanGestureRecognizer? private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? @@ -1819,6 +1828,7 @@ public class PremiumBoostLevelsScreen: ViewController { context: controller.context, theme: self.presentationData.theme, strings: self.presentationData.strings, + externalState: self.externalState, peerId: controller.peerId, mode: controller.mode, status: controller.status, @@ -1868,13 +1878,20 @@ public class PremiumBoostLevelsScreen: ViewController { var footerHeight: CGFloat = 8.0 + 50.0 footerHeight += layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : 8.0 + let actionTitle: String + if self.currentMyBoostCount > 0 { + actionTitle = self.presentationData.strings.ChannelBoost_BoostAgain + } else { + actionTitle = self.externalState.isGroup ? self.presentationData.strings.GroupBoost_BoostGroup : self.presentationData.strings.ChannelBoost_BoostChannel + } + let footerSize = self.footerView.update( transition: .immediate, component: AnyComponent( FooterComponent( context: controller.context, theme: self.presentationData.theme, - title: self.currentMyBoostCount > 0 ? "Boost Again" : "Boost Group", + title: actionTitle, action: { [weak self] in guard let self else { return diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index 8a5b48d65a..47ebea3c4a 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -279,7 +279,7 @@ final class PremiumStickerPackAccessoryNode: SparseNode, PeekControllerAccessory self.textNode.displaysAsynchronously = false self.textNode.textAlignment = .center self.textNode.maximumNumberOfLines = 0 - self.textNode.attributedText = NSAttributedString(string: isEmoji ? strings.Premium_Stickers_Description : strings.Premium_Stickers_Description, font: Font.regular(17.0), textColor: theme.actionSheet.secondaryTextColor) + self.textNode.attributedText = NSAttributedString(string: isEmoji ? strings.Premium_Emoji_Description : strings.Premium_Stickers_Description, font: Font.regular(17.0), textColor: theme.actionSheet.secondaryTextColor) self.textNode.lineSpacing = 0.1 self.proceedButton = SolidRoundedButtonNode(title: isEmoji ? strings.Premium_Emoji_Proceed: strings.Premium_Stickers_Proceed, theme: SolidRoundedButtonTheme( diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index a05de38cac..a9d665563c 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -900,21 +900,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) case let .setChatWallpaper(_, forBoth): + var isGroup = false + let messagePeer = message.peers[message.id.peerId] + if let channel = messagePeer as? TelegramChannel, case .group = channel.info { + isGroup = true + } if message.author?.id == accountPeerId { if forBoth { let peerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" let resultTitleString = strings.Notification_YouChangedWallpaperBoth(peerName) attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) } else { - attributedString = NSAttributedString(string: strings.Notification_YouChangedWallpaper, font: titleFont, textColor: primaryTextColor) + attributedString = NSAttributedString(string: isGroup ? strings.Notification_YouChangedGroupWallpaper : strings.Notification_YouChangedWallpaper, font: titleFont, textColor: primaryTextColor) } } else { if message.id.peerId.isGroupOrChannel { - var isGroup = false - let messagePeer = message.peers[message.id.peerId] - if let channel = messagePeer as? TelegramChannel, case .group = channel.info { - isGroup = true - } attributedString = NSAttributedString(string: isGroup ? strings.Notification_GroupChangedWallpaper : strings.Notification_ChannelChangedWallpaper, font: titleFont, textColor: primaryTextColor) } else { let resultTitleString = strings.Notification_ChangedWallpaper(compactAuthorName) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 039e6f062f..5c46c0dd0c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -371,13 +371,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, if let giveaway { if giveaway.flags.contains(.onlyNewSubscribers) { if giveaway.channelPeerIds.count > 1 { - participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsNewMany + participantsText = isGroup ? item.presentationData.strings.Chat_Giveaway_Message_Group_ParticipantsNewMany : item.presentationData.strings.Chat_Giveaway_Message_ParticipantsNewMany } else { - participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsNew + participantsText = isGroup ? item.presentationData.strings.Chat_Giveaway_Message_Group_ParticipantsNew : item.presentationData.strings.Chat_Giveaway_Message_ParticipantsNew } } else { if giveaway.channelPeerIds.count > 1 { - participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsMany + participantsText = isGroup ? item.presentationData.strings.Chat_Giveaway_Message_Group_ParticipantsMany : item.presentationData.strings.Chat_Giveaway_Message_ParticipantsMany } else { participantsText = isGroup ? item.presentationData.strings.Chat_Giveaway_Message_Group_Participants : item.presentationData.strings.Chat_Giveaway_Message_Participants } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift index b5adaa77d0..d3280e0622 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -265,7 +265,12 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode } let fromYou = item.message.author?.id == item.context.account.peerId - let isChannel = item.message.id.peerId.isGroupOrChannel + let isGroupOrChannel = item.message.id.peerId.isGroupOrChannel + var isGroup = false + let messagePeer = item.message.peers[item.message.id.peerId] + if let channel = messagePeer as? TelegramChannel, case .group = channel.info { + isGroup = true + } let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? "" let text: String @@ -278,19 +283,14 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode if forBoth { text = item.presentationData.strings.Notification_YouChangedWallpaperBoth(peerName).string } else { - text = item.presentationData.strings.Notification_YouChangedWallpaper + text = isGroup ? item.presentationData.strings.Notification_YouChangedGroupWallpaper : item.presentationData.strings.Notification_YouChangedWallpaper } } } else { if item.associatedData.isRecentActions { let authorName = item.message.author.flatMap { EnginePeer($0).compactDisplayTitle } ?? "" text = item.presentationData.strings.Channel_AdminLog_ChannelChangedWallpaper(authorName).string - } else if item.message.id.peerId.isGroupOrChannel { - var isGroup = false - let messagePeer = item.message.peers[item.message.id.peerId] - if let channel = messagePeer as? TelegramChannel, case .group = channel.info { - isGroup = true - } + } else if isGroupOrChannel { text = isGroup ? item.presentationData.strings.Notification_GroupChangedWallpaper : item.presentationData.strings.Notification_ChannelChangedWallpaper } else { text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string @@ -324,15 +324,15 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode if displayTrailingAnimatedDots { textHeight += subtitleLayout.size.height } - let backgroundSize = CGSize(width: width, height: textHeight + 140.0 + (fromYou || isChannel ? 0.0 : 42.0)) + let backgroundSize = CGSize(width: width, height: textHeight + 140.0 + (fromYou || isGroupOrChannel ? 0.0 : 42.0)) return (backgroundSize.width, { boundingWidth in return (backgroundSize, { [weak self] animation, synchronousLoads, _ in if let strongSelf = self { strongSelf.item = item - strongSelf.buttonNode.isHidden = fromYou || isChannel - strongSelf.buttonTitleNode.isHidden = fromYou || isChannel + strongSelf.buttonNode.isHidden = fromYou || isGroupOrChannel + strongSelf.buttonTitleNode.isHidden = fromYou || isGroupOrChannel let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: 13.0), size: imageSize) if let media, mediaUpdated { diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index e0a4acfe4a..a907c8376c 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -1698,11 +1698,15 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { var stickersEnabled = true var emojiEnabled = true if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel { - if peer.hasBannedPermission(.banSendStickers) != nil { - stickersEnabled = false - } - if peer.hasBannedPermission(.banSendText) != nil { - emojiEnabled = false + if let boostsToUnrestrict = interfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 { + + } else { + if peer.hasBannedPermission(.banSendStickers) != nil { + stickersEnabled = false + } + if peer.hasBannedPermission(.banSendText) != nil { + emojiEnabled = false + } } } else if let peer = interfaceState.renderedPeer?.peer as? TelegramGroup { if peer.hasBannedPermission(.banSendStickers) { diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index 985f1b80c9..6b920e2fdd 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -10,6 +10,9 @@ import ItemListUI import PresentationDataUtils import ActivityIndicator import StickerResources +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import ShimmerEffect import AppBundle enum GroupStickerPackCurrentItemContent: Equatable { @@ -94,6 +97,9 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { private let maskNode: ASImageNode fileprivate let imageNode: TransformImageNode + private var animationNode: AnimatedStickerNode? + private var placeholderNode: StickerShimmerEffectNode? + private let notFoundNode: ASImageNode private let titleNode: TextNode private let statusNode: TextNode @@ -133,6 +139,9 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { self.imageNode = TransformImageNode() self.imageNode.isLayerBacked = !smartInvertColorsEnabled() + self.placeholderNode = StickerShimmerEffectNode() + self.placeholderNode?.isUserInteractionEnabled = false + self.notFoundNode = ASImageNode() self.notFoundNode.isLayerBacked = true self.notFoundNode.displayWithoutProcessing = true @@ -161,6 +170,10 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + if let placeholderNode = self.placeholderNode { + self.addSubnode(placeholderNode) + } + self.addSubnode(self.imageNode) self.addSubnode(self.titleNode) self.addSubnode(self.statusNode) @@ -168,12 +181,50 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.activityIndicator) self.addSubnode(self.removeButton) + + var firstTime = true + self.imageNode.imageUpdated = { [weak self] image in + guard let strongSelf = self else { + return + } + if image != nil { + strongSelf.removePlaceholder(animated: !firstTime) + if firstTime { + strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + firstTime = false + } } deinit { self.fetchDisposable.dispose() } + private func removePlaceholder(animated: Bool) { + if let placeholderNode = self.placeholderNode { + self.placeholderNode = nil + if !animated { + placeholderNode.removeFromSupernode() + } else { + placeholderNode.allowsGroupOpacity = true + placeholderNode.alpha = 0.0 + placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in + placeholderNode?.removeFromSupernode() + placeholderNode?.allowsGroupOpacity = false + }) + } + } + } + + private var absoluteLocation: (CGRect, CGSize)? + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absoluteLocation = (rect, containerSize) + if let placeholderNode = placeholderNode { + placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize) + } + } + func asyncLayout() -> (_ item: GroupStickerPackCurrentItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeImageLayout = self.imageNode.asyncLayout() let makeTitleLayout = TextNode.asyncLayout(self.titleNode) @@ -300,22 +351,6 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { if strongSelf.maskNode.supernode == nil { strongSelf.addSubnode(strongSelf.maskNode) } -// switch neighbors.top { -// case .sameSection(false): -// strongSelf.topStripeNode.isHidden = true -// default: -// strongSelf.topStripeNode.isHidden = false -// } -// let bottomStripeInset: CGFloat -// let bottomStripeOffset: CGFloat -// switch neighbors.bottom { -// case .sameSection(false): -// bottomStripeInset = leftInset + editingOffset -// bottomStripeOffset = -separatorHeight -// default: -// bottomStripeInset = 0.0 -// bottomStripeOffset = 0.0 -// } let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false @@ -358,7 +393,6 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 32.0), size: statusLayout.size)) let boundingSize = CGSize(width: 34.0, height: 34.0) - transition.updateFrame(node: strongSelf.imageNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)) let indicatorSize = CGSize(width: 22.0, height: 22.0) transition.updateFrame(node: strongSelf.activityIndicator, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - indicatorSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)) @@ -369,9 +403,17 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.notFoundNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - image.size.width) / 2.0), y: 13.0 + floor((boundingSize.height - image.size.height) / 2.0)), size: image.size)) } + + if let updatedImageSignal = updatedImageSignal { strongSelf.imageNode.setSignal(updatedImageSignal) } + let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) + transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame) + + + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 6646f7a337..e1e7410355 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -1077,6 +1077,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen if hasSavedMessages, hasSavedMessagesChats, var availablePanesValue = availablePanes { if let index = availablePanesValue.firstIndex(of: .media) { availablePanesValue.insert(.savedMessages, at: index + 1) + } else if let index = availablePanesValue.firstIndex(of: .stories) { + availablePanesValue.insert(.savedMessages, at: index + 1) } else { availablePanesValue.insert(.savedMessages, at: 0) } @@ -1240,6 +1242,23 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen let requestsContextPromise = Promise(nil) let requestsStatePromise = Promise(nil) + let storyListContext: PeerStoryListContext? + let hasStories: Signal + if peerId.namespace == Namespaces.Peer.CloudChannel { + storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false) + hasStories = storyListContext!.state + |> map { state -> Bool? in + if !state.hasCache { + return nil + } + return !state.items.isEmpty + } + |> distinctUntilChanged + } else { + storyListContext = nil + hasStories = .single(false) + } + let threadData: Signal if case let .replyThread(message) = chatLocation { let threadId = message.threadId @@ -1306,6 +1325,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitationsStatePromise.get(), requestsContextPromise.get(), requestsStatePromise.get(), + hasStories, threadData, context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), accountIsPremium, @@ -1313,12 +1333,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats, hasSavedMessageTags ) - |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal in + |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal in var discussionPeer: Peer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer } - + var availablePanes = availablePanes if let membersData = membersData, case .longList = membersData { if availablePanes != nil { @@ -1328,17 +1348,24 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } } - if case .peer = chatLocation { - if hasSavedMessages, hasSavedMessagesChats, var availablePanesValue = availablePanes { - if let index = availablePanesValue.firstIndex(of: .media) { - availablePanesValue.insert(.savedMessages, at: index + 1) - } else { - availablePanesValue.insert(.savedMessages, at: 0) + if let hasStories { + if hasStories { + availablePanes?.insert(.stories, at: 0) + } + if case .peer = chatLocation { + if hasSavedMessages, hasSavedMessagesChats, var availablePanesValue = availablePanes { + if let index = availablePanesValue.firstIndex(of: .media) { + availablePanesValue.insert(.savedMessages, at: index + 1) + } else if let index = availablePanesValue.firstIndex(of: .stories) { + availablePanesValue.insert(.savedMessages, at: index + 1) + } else { + availablePanesValue.insert(.savedMessages, at: 0) + } + availablePanes = availablePanesValue } - availablePanes = availablePanesValue } } - + var canManageInvitations = false if let group = peerViewMainPeer(peerView) as? TelegramGroup { let previousValue = wasUpgradedGroup.swap(group.migrationReference != nil) @@ -1389,7 +1416,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen groupsInCommon: nil, linkedDiscussionPeer: discussionPeer, members: membersData, - storyListContext: nil, + storyListContext: storyListContext, encryptionKeyFingerprint: nil, globalSettings: nil, invitations: invitations, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 7556a3c249..96ad8142f1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -10375,7 +10375,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) } - if let data = self.data, data.accountIsPremium, let channel = data.peer as? TelegramChannel, case .broadcast = channel.info, channel.hasPermission(.postStories) { + if let data = self.data, data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) { rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0) } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 2dcb381b36..54b8809c26 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -540,6 +540,10 @@ final class ChannelAppearanceScreenComponent: Component { if self.isApplyingSettings { return } + if resolvedState.changes.isEmpty { + self.environment?.controller()?.dismiss() + return + } let requiredLevel = requiredBoostSubject.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift index 5835d255e3..62e79340c7 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift @@ -1601,10 +1601,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } if let mode = self.mode, case let .peer(peer, existing) = mode { - if case .channel = peer { + if case let .channel(channel) = peer { topMessageText = presentationData.strings.WallpaperPreview_ChannelTopText bottomMessageText = "" - serviceMessageText = presentationData.strings.WallpaperPreview_ChannelHeader + switch channel.info { + case .group: + serviceMessageText = presentationData.strings.WallpaperPreview_GroupHeader + case .broadcast: + serviceMessageText = presentationData.strings.WallpaperPreview_ChannelHeader + } } else { topMessageText = presentationData.strings.WallpaperPreview_ChatTopText bottomMessageText = presentationData.strings.WallpaperPreview_ChatBottomText diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 6528b0fefa..5995288b2e 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -544,8 +544,12 @@ final class ShareWithPeersScreenComponent: Component { case .pin: if self.selectedOptions.contains(.pin) { animationName = "anim_profileadd" - if let peerId = self.sendAsPeerId, peerId.namespace != Namespaces.Peer.CloudUser { - text = presentationData.strings.Story_Privacy_TooltipStoryArchivedChannel + if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel { + if let selectedPeer = self.effectiveStateValue?.sendAsPeers.first(where: { $0.id == sendAsPeerId }), case let .channel(channel) = selectedPeer, case .group = channel.info { + text = presentationData.strings.Story_Privacy_TooltipStoryArchivedGroup + } else { + text = presentationData.strings.Story_Privacy_TooltipStoryArchivedChannel + } } else { text = presentationData.strings.Story_Privacy_TooltipStoryArchived } @@ -837,6 +841,15 @@ final class ShareWithPeersScreenComponent: Component { return } + var isSendAsGroup = false + if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true { + if let selectedPeer = stateValue.sendAsPeers.first(where: { $0.id == sendAsPeerId }) { + if case let .channel(channel) = selectedPeer, case .group = channel.info { + isSendAsGroup = true + } + } + } + var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset topOffset = max(0.0, topOffset) transition.setTransform(layer: self.backgroundView.layer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) @@ -939,10 +952,10 @@ final class ShareWithPeersScreenComponent: Component { } else if section.id == 2 { sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader } else if section.id == 1 { - if case .members = component.stateContext.subject { - sectionTitle = environment.strings.BoostGift_Subscribers_SectionTitle - } else if case .channels = component.stateContext.subject { - sectionTitle = environment.strings.BoostGift_Channels_SectionTitle + if case let .members(isGroup, _, _) = component.stateContext.subject { + sectionTitle = isGroup ? environment.strings.BoostGift_Members_SectionTitle : environment.strings.BoostGift_Subscribers_SectionTitle + } else if case let .channels(isGroup, _, _) = component.stateContext.subject { + sectionTitle = isGroup ? environment.strings.BoostGift_GroupsOrChannels_SectionTitle : environment.strings.BoostGift_ChannelsOrGroups_SectionTitle } else { sectionTitle = environment.strings.Story_Privacy_ContactsHeader } @@ -1494,11 +1507,11 @@ final class ShareWithPeersScreenComponent: Component { update() } } else { - if case .members = component.stateContext.subject, self.selectedPeers.count >= 10, index == nil { + if case let .members(isGroup, _, _) = component.stateContext.subject, self.selectedPeers.count >= 10, index == nil { self.hapticFeedback.error() let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: environment.strings.BoostGift_Subscribers_MaximumReached("\(10)").string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) + controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: isGroup ? environment.strings.BoostGift_Members_MaximumReached("\(10)").string : environment.strings.BoostGift_Subscribers_MaximumReached("\(10)").string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) return } togglePeer() @@ -1541,7 +1554,7 @@ final class ShareWithPeersScreenComponent: Component { var title = item.title if item.id == .pin && !hasCategories { - title = environment.strings.Story_Privacy_KeepOnChannelPage + title = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPage : environment.strings.Story_Privacy_KeepOnChannelPage } let _ = visibleItem.update( @@ -1595,8 +1608,8 @@ final class ShareWithPeersScreenComponent: Component { let footerValue = environment.strings.Story_Privacy_KeepOnMyPageHours(Int32(component.timeout / 3600)) var footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string - if self.sendAsPeerId?.isGroupOrChannel == true { - footerText = environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string + if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true { + footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string } let footerSize = sectionFooter.update( @@ -1701,7 +1714,7 @@ final class ShareWithPeersScreenComponent: Component { if let searchStateContext = self.searchStateContext, let value = searchStateContext.stateValue { if case let .contactsSearch(query, _) = searchStateContext.subject { searchQuery = query - } else if case let .members(_, query) = searchStateContext.subject { + } else if case let .members(_, _, query) = searchStateContext.subject { searchQuery = query } else if case let .channels(_, _, query) = searchStateContext.subject { searchQuery = query @@ -2040,10 +2053,10 @@ final class ShareWithPeersScreenComponent: Component { let placeholder: String switch component.stateContext.subject { - case .members: - placeholder = environment.strings.BoostGift_Subscribers_Search - case .channels: - placeholder = environment.strings.BoostGift_Channels_Search + case let .members(isGroup, _, _): + placeholder = isGroup ? environment.strings.BoostGift_Members_Search : environment.strings.BoostGift_Subscribers_Search + case let .channels(isGroup, _, _): + placeholder = isGroup ? environment.strings.BoostGift_GroupsOrChannels_Search : environment.strings.BoostGift_ChannelsOrGroups_Search case .chats: placeholder = environment.strings.Story_Privacy_SearchChats default: @@ -2090,8 +2103,8 @@ final class ShareWithPeersScreenComponent: Component { switch component.stateContext.subject { case let .channels(isGroup, exclude, _): searchSubject = .channels(isGroup: isGroup, exclude: exclude, searchQuery: searchQuery) - case let .members(peerId, _): - searchSubject = .members(peerId: peerId, searchQuery: searchQuery) + case let .members(isGroup, peerId, _): + searchSubject = .members(isGroup: isGroup, peerId: peerId, searchQuery: searchQuery) default: searchSubject = .contactsSearch(query: searchQuery, onlyContacts: onlyContacts) } @@ -2389,9 +2402,9 @@ final class ShareWithPeersScreenComponent: Component { } case .contactsSearch: title = "" - case .members: + case let .members(isGroup, _, _): title = environment.strings.BoostGift_Subscribers_Title - subtitle = environment.strings.BoostGift_Subscribers_Subtitle("\(10)").string + subtitle = isGroup ? environment.strings.BoostGift_Members_Subtitle("\(10)").string : environment.strings.BoostGift_Subscribers_Subtitle("\(10)").string actionButtonTitle = environment.strings.BoostGift_Subscribers_Save case let .channels(isGroup, _, _): title = isGroup ? environment.strings.BoostGift_GroupsOrChannels_Title : environment.strings.BoostGift_ChannelsOrGroups_Title diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift index e84c619727..6ec3c19b0a 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift @@ -48,7 +48,7 @@ public extension ShareWithPeersScreen { case chats(blocked: Bool) case contacts(base: EngineStoryPrivacy.Base) case contactsSearch(query: String, onlyContacts: Bool) - case members(peerId: EnginePeer.Id, searchQuery: String?) + case members(isGroup: Bool, peerId: EnginePeer.Id, searchQuery: String?) case channels(isGroup: Bool, exclude: Set, searchQuery: String?) } @@ -515,7 +515,7 @@ public extension ShareWithPeersScreen { self.readySubject.set(true) }) - case let .members(peerId, searchQuery): + case let .members(_, peerId, searchQuery): let membersState = Promise() let contactsState = Promise() diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index c5dc13c766..456dde1f03 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -318,7 +318,7 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati } else { banDescription = presentationData.strings.Conversation_DefaultRestrictedMedia } - emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .bannedSendMedia(banDescription)) + emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .bannedSendMedia(text: banDescription, canBoost: false)) } else if let recentDocuments = recentDocuments, recentDocuments.isEmpty { emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .intro) } diff --git a/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift b/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift index 2ede2b3ced..f76d478415 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift @@ -8,11 +8,12 @@ import PresentationDataUtils import AnimatedStickerNode import TelegramAnimatedStickerNode import AccountContext +import SolidRoundedButtonNode final class AttachmentFileEmptyStateItem: ItemListControllerEmptyStateItem { enum Content: Equatable { case intro - case bannedSendMedia(String) + case bannedSendMedia(text: String, canBoost: Bool) } let context: AccountContext @@ -48,6 +49,7 @@ final class AttachmentFileEmptyStateItem: ItemListControllerEmptyStateItem { final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNode { private var animationNode: AnimatedStickerNode private let textNode: ASTextNode + private let buttonNode: SolidRoundedButtonNode private var validLayout: (ContainerViewLayout, CGFloat)? var item: AttachmentFileEmptyStateItem { @@ -62,8 +64,19 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo init(item: AttachmentFileEmptyStateItem) { self.item = item + let name: String + let playbackMode: AnimatedStickerPlaybackMode + switch item.content { + case .intro: + name = "Files" + playbackMode = .loop + case .bannedSendMedia: + name = "Banned" + playbackMode = .once + } + self.animationNode = DefaultAnimatedStickerNodeImpl() - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Files"), width: 320, height: 320, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: name), width: 320, height: 320, playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil)) self.animationNode.visibility = true self.textNode = ASTextNode() @@ -71,6 +84,8 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo self.textNode.lineSpacing = 0.1 self.textNode.textAlignment = .center + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true) + super.init() self.isUserInteractionEnabled = false @@ -79,6 +94,10 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo self.addSubnode(self.textNode) self.updateThemeAndStrings(theme: self.item.theme, strings: self.item.strings) + + if case .bannedSendMedia(_, true) = item.content { + self.addSubnode(self.buttonNode) + } } private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { @@ -86,46 +105,47 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo switch self.item.content { case .intro: text = strings.Attachment_FilesIntro - case let .bannedSendMedia(banDescription): + case let .bannedSendMedia(banDescription, _): text = banDescription } self.textNode.attributedText = NSAttributedString(string: text.replacingOccurrences(of: "\n", with: " "), font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) + self.buttonNode.title = strings.Attachment_OpenSettings + self.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: theme)) } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (layout, navigationBarHeight) - var insets = layout.insets(options: []) - insets.top += navigationBarHeight - - let imageSpacing: CGFloat = 12.0 - + var imageSize = CGSize(width: 144.0, height: 144.0) + var insets = layout.insets(options: []) if layout.size.width == 320.0 { + insets.top += -60.0 imageSize = CGSize(width: 112.0, height: 112.0) + } else { + insets.top += -160.0 } + let imageSpacing: CGFloat = 12.0 + let textSpacing: CGFloat = 12.0 + let buttonSpacing: CGFloat = 15.0 + let bottomSpacing: CGFloat = 33.0 + let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 - if !imageHeight.isZero { - if case .intro = self.item.content { - insets.top -= 92.0 - } else { - insets.top -= 160.0 - } - } - if layout.size.width == 320.0 { - insets.top += 110.0 - } - - let textSize = self.textNode.measure(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 70.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let buttonWidth: CGFloat = 248.0 + let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition) - let totalHeight = imageHeight + textSize.height + let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + + let totalHeight = imageHeight + textSpacing + textSize.height + buttonSpacing + buttonHeight + bottomSpacing let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0) transition.updateAlpha(node: self.animationNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0) transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize)) self.animationNode.updateLayout(size: imageSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: topOffset + imageHeight), size: textSize)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: topOffset + imageHeight + textSpacing), size: textSize)) + + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - buttonWidth - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: self.textNode.frame.maxY + buttonSpacing), size: CGSize(width: buttonWidth, height: buttonHeight))) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 8f2d03766c..184a26d742 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1692,6 +1692,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendStickers) != nil { + if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict { + strongSelf.interfaceInteraction?.openBoostToUnrestrict() + return false + } + } + var attributes: [MessageAttribute] = [] if let query = query { attributes.append(EmojiSearchQueryMessageAttribute(query: query)) @@ -1837,6 +1844,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil { + if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict { + strongSelf.interfaceInteraction?.openBoostToUnrestrict() + return false + } + } + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() @@ -1881,6 +1895,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil { + if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict { + strongSelf.interfaceInteraction?.openBoostToUnrestrict() + return false + } + } + strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true, silentPosting: silentPosting, resetTextInputState: resetTextInputState) return true @@ -9538,10 +9559,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - if let boostsToUnrestrict = (strongSelf.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0 { - strongSelf.interfaceInteraction?.openBoostToUnrestrict() - return - } + + let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) let subjectFlags: [TelegramChatBannedRightsFlags] switch subject { @@ -9554,7 +9573,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var bannedPermission: (Int32, Bool)? = nil if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel { for subjectFlag in subjectFlags { - if let value = channel.hasBannedPermission(subjectFlag) { + if let value = channel.hasBannedPermission(subjectFlag, ignoreDefault: canBypassRestrictions) { bannedPermission = value break } @@ -9568,6 +9587,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + if let boostsToUnrestrict = (strongSelf.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0, bannedPermission == nil { + strongSelf.interfaceInteraction?.openBoostToUnrestrict() + return + } + var displayToast = false if let (untilDate, personal) = bannedPermission { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 3c9ab0de89..a72fd06e61 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -44,11 +44,12 @@ extension ChatControllerImpl { } let context = self.context - let inputIsActive = self.presentationInterfaceState.inputMode == .text self.chatDisplayNode.dismissInput() - + + let canByPassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: self.presentationInterfaceState) + var banSendText: (Int32, Bool)? var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? @@ -60,19 +61,19 @@ extension ChatControllerImpl { } else if peer is TelegramSecretChat { canSendPolls = false } else if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { + if let value = channel.hasBannedPermission(.banSendPhotos, ignoreDefault: canByPassRestrictions) { bannedSendPhotos = value } - if let value = channel.hasBannedPermission(.banSendVideos) { + if let value = channel.hasBannedPermission(.banSendVideos, ignoreDefault: canByPassRestrictions) { bannedSendVideos = value } - if let value = channel.hasBannedPermission(.banSendFiles) { + if let value = channel.hasBannedPermission(.banSendFiles, ignoreDefault: canByPassRestrictions) { bannedSendFiles = value } - if let value = channel.hasBannedPermission(.banSendText) { + if let value = channel.hasBannedPermission(.banSendText, ignoreDefault: canByPassRestrictions) { banSendText = value } - if channel.hasBannedPermission(.banSendPolls) != nil { + if channel.hasBannedPermission(.banSendPolls, ignoreDefault: canByPassRestrictions) != nil { canSendPolls = false } } else if let group = peer as? TelegramGroup { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 86b6179f78..4c87600f81 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -201,8 +201,12 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) { accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting)) } - if peer.hasBannedPermission(.banSendStickers) != nil { - stickersEnabled = false + if let boostsToUnrestrict = chatPresentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 { + + } else { + if peer.hasBannedPermission(.banSendStickers) != nil { + stickersEnabled = false + } } } else if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup { if peer.hasBannedPermission(.banSendStickers) { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 106101d26b..d164dd3373 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -4743,13 +4743,17 @@ private final class BoostSlowModeButton: HighlightTrackingButtonNode { case .pendingMessages: relativeTimestamp = CGFloat(slowmodeState.timeout) } - text = stringForDuration(Int32(relativeTimestamp)) self.updateTimer?.invalidate() - self.updateTimer = SwiftSignalKit.Timer(timeout: 1.0 / 60.0, repeat: false, completion: { [weak self] in - self?.requestUpdate() - }, queue: .mainQueue()) - self.updateTimer?.start() + + if relativeTimestamp >= 0.0 { + text = stringForDuration(Int32(relativeTimestamp)) + + self.updateTimer = SwiftSignalKit.Timer(timeout: 1.0 / 60.0, repeat: false, completion: { [weak self] in + self?.requestUpdate() + }, queue: .mainQueue()) + self.updateTimer?.start() + } } else { self.updateTimer?.invalidate() self.updateTimer = nil