diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index ec41a56443..78a76633bd 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8939,3 +8939,5 @@ Sorry for the inconvenience."; "ChatList.ClearSavedMessagesConfirmation" = "Are you sure you want to delete all your saved messages?"; "Conversation.Translation.Settings" = "Settings"; +"GroupPermission.NotAvailableInDiscussionGroups" = "This permission is not available in discussion groups."; +"GroupPermission.NotAvailableInGeoGroups" = "This permission is not available in location-based groups."; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 087c8ed5c7..e84d200bc5 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -665,7 +665,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if threadId == 1 { titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme)) } else if let fileId = iconId, fileId != 0 { - titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2)) + titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) } else { titleTopicIconContent = .topic(title: String(title.string.prefix(1)), color: iconColor, size: CGSize(width: 18.0, height: 18.0)) } @@ -2778,7 +2778,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if threadInfo.id == 1 { avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme)) } else if let fileId = threadInfo.info.icon, fileId != 0 { - avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0)) } else { avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), color: threadInfo.info.iconColor, size: CGSize(width: 32.0, height: 32.0)) } diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index 4bd4dcd33f..9b5f5b7917 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -84,7 +84,7 @@ public final class JoinLinkPreviewController: ViewController { strongSelf.isGroup = !invite.flags.isBroadcast strongSelf.controllerNode.setRequestPeer(image: invite.photoRepresentation, title: invite.title, about: invite.about, memberCount: invite.participantsCount, isGroup: !invite.flags.isBroadcast) } else { - let data = JoinLinkPreviewData(isGroup: invite.participants != nil, isJoined: false) + let data = JoinLinkPreviewData(isGroup: !invite.flags.isBroadcast, isJoined: false) strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data) } case let .alreadyJoined(peer): diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 15589e06d0..05cabcdd74 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -461,7 +461,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender } } - func gcd(_ a: Int32, _ b: Int32) -> Int32 { + func gcd(_ a: Int64, _ b: Int64) -> Int64 { let remainder = a % b if remainder != 0 { return gcd(b, remainder) @@ -470,7 +470,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender } } - func lcm(_ x: Int32, _ y: Int32) -> Int32 { + func lcm(_ x: Int64, _ y: Int64) -> Int64 { return x / gcd(x, y) * y } @@ -480,7 +480,7 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender let minDuration: Double = 3.0 if durations.count > 1 { let reduced = durations.reduce(1.0) { lhs, rhs -> Double in - return Double(lcm(Int32(lhs * 10.0), Int32(rhs * 10.0))) + return Double(lcm(Int64(lhs * 10.0), Int64(rhs * 10.0))) } result = min(6.0, Double(reduced) / 10.0) } else if let duration = durations.first { diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift index 6ca0d5fc0f..d546047a88 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift @@ -36,6 +36,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode { private let modalPresentation: Bool + private let coverNode: ASDisplayNode private var backgroundCustomNode: ASDisplayNode? private let backgroundDimNode: ASDisplayNode private let backgroundImageNode: ASImageNode @@ -70,6 +71,9 @@ final class PasscodeEntryControllerNode: ASDisplayNode { self.arguments = arguments self.modalPresentation = modalPresentation + self.coverNode = ASDisplayNode() + self.coverNode.backgroundColor = .black + self.backgroundImageNode = ASImageNode() self.backgroundImageNode.contentMode = .scaleToFill @@ -140,6 +144,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode { } } + self.addSubnode(self.coverNode) self.addSubnode(self.backgroundImageNode) self.addSubnode(self.backgroundDimNode) self.addSubnode(self.iconNode) @@ -238,8 +243,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode { color3 = baseColor.withMultiplied(hue: 1.029, saturation: 0.729, brightness: 1.231) color4 = baseColor.withMultiplied(hue: 1.034, saturation: 0.583, brightness: 1.043) } + self.coverNode.backgroundColor = color3 self.background = CustomPasscodeBackground(size: size, colors: [color1, color2, color3, color4], inverted: false) case let .gradient(gradient): + self.coverNode.backgroundColor = gradient.colors.first.flatMap { UIColor(rgb: $0) } self.background = CustomPasscodeBackground(size: size, colors: gradient.colors.compactMap { UIColor(rgb: $0) }, inverted: (gradient.settings.intensity ?? 0) < 0) case .image, .file: if let image = chatControllerBackgroundImage(theme: self.theme, wallpaper: self.wallpaper, mediaBox: self.accountManager.mediaBox, composed: false, knockoutMode: false) { @@ -252,6 +259,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode { } } default: + self.coverNode.backgroundColor = self.theme.passcode.backgroundColors.bottomColor self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor) } @@ -355,6 +363,8 @@ final class PasscodeEntryControllerNode: ASDisplayNode { } func animateIn(iconFrame: CGRect, completion: @escaping () -> Void = {}) { + self.coverNode.isHidden = true + let effect = self.theme.overallDarkAppearance ? UIBlurEffect(style: .dark) : UIBlurEffect(style: .light) UIView.animate(withDuration: 0.3, animations: { if #available(iOS 9.0, *) { @@ -386,6 +396,8 @@ final class PasscodeEntryControllerNode: ASDisplayNode { self.biometricButtonNode.isHidden = true self.titleNode.setAttributedText(NSAttributedString(string: self.strings.Passcode_AppLockedAlert.replacingOccurrences(of: "\n", with: " "), font: titleFont, textColor: .white), animation: .slideIn, completion: { + self.coverNode.isHidden = false + self.subtitleNode.isHidden = false self.inputFieldNode.isHidden = false self.keyboardNode.isHidden = false @@ -447,6 +459,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode { self.validLayout = layout self.updateBackground() + + let maxSide = max(layout.size.width, layout.size.height) + let coverSize = CGSize(width: maxSide, height: maxSide) + self.coverNode.frame = CGRect(origin: CGPoint(x: round((layout.size.width - coverSize.width) / 2.0), y: round((layout.size.height - coverSize.height) / 2.0)), size: coverSize) let bounds = CGRect(origin: CGPoint(), size: layout.size) transition.updateFrame(node: self.backgroundImageNode, frame: bounds) diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 5fcd65d6a4..05415756a0 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -547,6 +547,11 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen var entries: [ChannelPermissionsEntry] = [] if let channel = view.peers[view.peerId] as? TelegramChannel, let participants = participants, let cachedData = view.cachedData as? CachedChannelData, let defaultBannedRights = channel.defaultBannedRights { + var isDiscussion = false + if case .group = channel.info, case let .known(peerId) = cachedData.linkedDiscussionPeerId, peerId != nil { + isDiscussion = true + } + let effectiveRightsFlags: TelegramChatBannedRightsFlags if let modifiedRightsFlags = state.modifiedRightsFlags { effectiveRightsFlags = modifiedRightsFlags @@ -558,7 +563,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen var rightIndex: Int = 0 for (rights, correspondingAdminRight) in allGroupPermissionList(peer: .channel(channel), expandMedia: false) { var enabled = true - if channel.addressName != nil && publicGroupRestrictedPermissions.contains(rights) { + if (channel.addressName != nil || channel.flags.contains(.hasGeo) || isDiscussion) && publicGroupRestrictedPermissions.contains(rights) { enabled = false } if !channel.hasPermission(.inviteMembers) { @@ -938,7 +943,17 @@ public func channelPermissionsController(context: AccountContext, updatedPresent } else if right.contains(.banAddMembers) { text = presentationData.strings.GroupPermission_AddMembersNotAvailable } else { - text = presentationData.strings.GroupPermission_NotAvailableInPublicGroups + var isDiscussion = false + if case .group = channel.info, let cachedData = view.cachedData as? CachedChannelData, case let .known(peerId) = cachedData.linkedDiscussionPeerId, peerId != nil { + isDiscussion = true + } + if channel.flags.contains(.hasGeo) { + text = presentationData.strings.GroupPermission_NotAvailableInGeoGroups + } else if isDiscussion { + text = presentationData.strings.GroupPermission_NotAvailableInDiscussionGroups + } else { + text = presentationData.strings.GroupPermission_NotAvailableInPublicGroups + } } presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) break diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift index fd2d4197ca..90d653e163 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift @@ -217,7 +217,7 @@ public extension TelegramChannel { if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banChangeInfo) { return false } - return false + return true } case .addAdmins: if let adminRights = self.adminRights, adminRights.rights.contains(.canAddAdmins) { diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 2fbbf956d0..8d2c6c72b6 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -396,7 +396,7 @@ private final class TopicIconSelectionComponent: Component { component: AnyComponent(EntityKeyboardComponent( theme: component.theme, strings: component.strings, - isContentInFocus: true, + isContentInFocus: false, containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: 0.0, right: 0.0), topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), emojiContent: component.emojiContent, diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 116ee94796..a8c32b2297 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1467,7 +1467,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState break } } - if let file = media as? TelegramMediaFile, !chatPresentationInterfaceState.copyProtectionEnabled && !message.isCopyProtected() { + if let file = media as? TelegramMediaFile, !isCopyProtected { if file.isVideo { if file.isAnimated && !file.isVideoSticker { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveGif, icon: { theme in @@ -1518,11 +1518,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState f(.dismissWithoutContent) }))) } - + if data.messageActions.options.contains(.forward) { - if chatPresentationInterfaceState.copyProtectionEnabled || message.isCopyProtected() { - - } else { + if !isCopyProtected { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 64f44ec677..521292bd76 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1298,8 +1298,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - if !needsShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { - needsShareButton = true + if !needsShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo { + if !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { + needsShareButton = true + } else if author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(92386307)) { + needsShareButton = true + } } var mayHaveSeparateCommentsButton = false if !needsShareButton { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 7f27649fdb..197f0de09d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1850,27 +1850,43 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL interaction.editingOpenPreHistorySetup() })) - do { - let label: String - if let cachedData = data.cachedData as? CachedGroupData, case let .known(allowedReactions) = cachedData.allowedReactions { - switch allowedReactions { - case .all: - label = presentationData.strings.PeerInfo_LabelAllReactions - case .empty: - label = presentationData.strings.PeerInfo_ReactionsDisabled - case let .limited(reactions): - label = "\(reactions.count)" - } - } else { - label = "" + + let label: String + if let cachedData = data.cachedData as? CachedGroupData, case let .known(allowedReactions) = cachedData.allowedReactions { + switch allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + label = presentationData.strings.PeerInfo_ReactionsDisabled + case let .limited(reactions): + label = "\(reactions.count)" } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { - interaction.editingOpenReactionsSetup() - })) + } else { + label = "" } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) canViewAdminsAndBanned = true } else if case let .admin(rights, _) = group.role { + let label: String + if let cachedData = data.cachedData as? CachedGroupData, case let .known(allowedReactions) = cachedData.allowedReactions { + switch allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + label = presentationData.strings.PeerInfo_ReactionsDisabled + case let .limited(reactions): + label = "\(reactions.count)" + } + } else { + label = "" + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) + if rights.rights.contains(.canInviteUsers) { let invitesText: String if let count = data.invitations?.count, count > 0 { @@ -3440,10 +3456,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate })) } + var previousTimestamp: Double? self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _, _ in guard let strongSelf = self else { return } + let currentTimestamp = CACurrentMediaTime() + if let previousTimestamp, currentTimestamp < previousTimestamp + 1.0 { + return + } + previousTimestamp = currentTimestamp let animationCache = context.animationCache let animationRenderer = context.animationRenderer @@ -3494,10 +3516,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } else { screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder) + var previousTimestamp: Double? self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in guard let strongSelf = self else { return } + let currentTimestamp = CACurrentMediaTime() + if let previousTimestamp, currentTimestamp < previousTimestamp + 1.0 { + return + } + previousTimestamp = currentTimestamp let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) guard !premiumConfiguration.isPremiumDisabled else {