From d761d9314c2709212025bfbdefa518ff9c6c0fff Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 1 Sep 2024 10:32:03 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../BrowserUI/Sources/BrowserScreen.swift | 4 +- .../Sources/ContactsControllerNode.swift | 14 +- .../Sources/CreateGiveawayController.swift | 6 +- .../Sources/ChannelStatsController.swift | 4 +- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api19.swift | 18 +- .../TelegramEngine/Payments/GiftCodes.swift | 6 +- .../Sources/ChatMessageDateHeader.swift | 2 +- .../InstantViewOff.imageset/Contents.json | 12 + .../InstantViewOff.imageset/boostoff_24.pdf | Bin 0 -> 1789 bytes .../TelegramUI/Sources/AccountContext.swift | 6 +- .../TelegramUI/Sources/ChatController.swift | 383 ++++++++++++++++-- 13 files changed, 381 insertions(+), 79 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/boostoff_24.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a9a0444ed4..805a41a728 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12880,3 +12880,6 @@ Sorry for the inconvenience."; "BoostGift.PrepaidGiveawayStarsCount_1" = "%@ Telegram Premium"; "BoostGift.PrepaidGiveawayStarsCount_any" = "%@ Telegram Premium"; "BoostGift.PrepaidGiveawayStarsMonths" = "%@-month subscriptions"; + +"WebBrowser.ShowInstantView" = "Show Instant View"; +"WebBrowser.HideInstantView" = "Hide Instant View"; diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 704a3bcdf2..be250f15ef 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -1175,12 +1175,12 @@ public class BrowserScreen: ViewController, MinimizableController { if case .webPage = contentState.contentType { let isAvailable = contentState.hasInstantView - items.append(.action(ContextMenuActionItem(text: "Show Instant View", textColor: isAvailable ? .primary : .disabled, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: isAvailable ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withAlphaComponent(0.3)) }, action: isAvailable ? { (controller, action) in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_ShowInstantView, textColor: isAvailable ? .primary : .disabled, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: isAvailable ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withAlphaComponent(0.3)) }, action: isAvailable ? { (controller, action) in performAction.invoke(.toggleInstantView(true)) action(.default) } : nil))) } else if case .instantPage = contentState.contentType, contentState.isInnerInstantViewEnabled { - items.append(.action(ContextMenuActionItem(text: "Hide Instant View", textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_HideInstantView, textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/InstantViewOff"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in performAction.invoke(.toggleInstantView(false)) action(.default) }))) diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 7f2fd359c9..36d11ff2a3 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -93,17 +93,13 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.stringsPromise.set(.single(self.presentationData.strings)) - var addNearbyImpl: (() -> Void)? var inviteImpl: (() -> Void)? let presentation = combineLatest(sortOrder, self.stringsPromise.get()) |> map { sortOrder, strings -> ContactListPresentation in - let options = [ContactListAdditionalOption(title: strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: { - addNearbyImpl?() - }), ContactListAdditionalOption(title: strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: { + let options = [ContactListAdditionalOption(title: strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: { inviteImpl?() })] - switch sortOrder { case .presence: return .orderedByPresence(options: options) @@ -145,13 +141,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { } } }).strict() - - addNearbyImpl = { [weak self] in - if let strongSelf = self { - strongSelf.openPeopleNearby?() - } - } - + inviteImpl = { [weak self] in if let strongSelf = self { strongSelf.openInvite?() diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 37d71f5657..b50acdf9ef 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -471,7 +471,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { default: color = .blue } - case let .stars(amount): + case let .stars(amount, _): if amount <= 1000 { color = .green } else if amount < 2500 { @@ -777,7 +777,7 @@ private func createGiveawayControllerEntries( case let .premium(months): title = presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity) text = presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(months)").string - case let .stars(stars): + case let .stars(stars, _): //TODO:localize title = "\(stars) Telegram Stars" text = "among \(prepaidGiveaway.quantity) winners" @@ -1013,7 +1013,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio let initialStars: Int64 let initialWinners: Int32 if case let .prepaid(prepaidGiveaway) = subject { - if case let .stars(stars) = prepaidGiveaway.prize { + if case let .stars(stars, _) = prepaidGiveaway.prize { initialStars = stars } else { initialStars = 500 diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 5000d945ec..830e32543f 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1050,7 +1050,7 @@ private enum StatsEntry: ItemListNodeEntry { default: color = .blue } - case let .stars(amount): + case let .stars(amount, _): if amount <= 1000 { color = .green } else if amount < 2500 { @@ -1440,7 +1440,7 @@ private func boostsEntries( case let .premium(months): title = presentationData.strings.Stats_Boosts_PrepaidGiveawayCount(giveaway.quantity) text = presentationData.strings.Stats_Boosts_PrepaidGiveawayMonths("\(months)").string - case let .stars(stars): + case let .stars(stars, _): title = "\(stars) Telegram Stars" text = "among \(giveaway.quantity) winners" } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index d133abf911..f093c8b330 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -746,7 +746,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) } dict[1596792306] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) } dict[-1303143084] = { return Api.PrepaidGiveaway.parse_prepaidGiveaway($0) } - dict[-1973402371] = { return Api.PrepaidGiveaway.parse_prepaidStarsGiveaway($0) } + dict[-1700956192] = { return Api.PrepaidGiveaway.parse_prepaidStarsGiveaway($0) } dict[-1534675103] = { return Api.PrivacyKey.parse_privacyKeyAbout($0) } dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) } dict[536913176] = { return Api.PrivacyKey.parse_privacyKeyBirthday($0) } diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 3690ab6ce7..7935cc9ca1 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -1013,7 +1013,7 @@ public extension Api { public extension Api { enum PrepaidGiveaway: TypeConstructorDescription { case prepaidGiveaway(id: Int64, months: Int32, quantity: Int32, date: Int32) - case prepaidStarsGiveaway(id: Int64, stars: Int64, quantity: Int32, date: Int32) + case prepaidStarsGiveaway(id: Int64, stars: Int64, quantity: Int32, boosts: Int32, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1026,13 +1026,14 @@ public extension Api { serializeInt32(quantity, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break - case .prepaidStarsGiveaway(let id, let stars, let quantity, let date): + case .prepaidStarsGiveaway(let id, let stars, let quantity, let boosts, let date): if boxed { - buffer.appendInt32(-1973402371) + buffer.appendInt32(-1700956192) } serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(stars, buffer: buffer, boxed: false) serializeInt32(quantity, buffer: buffer, boxed: false) + serializeInt32(boosts, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break } @@ -1042,8 +1043,8 @@ public extension Api { switch self { case .prepaidGiveaway(let id, let months, let quantity, let date): return ("prepaidGiveaway", [("id", id as Any), ("months", months as Any), ("quantity", quantity as Any), ("date", date as Any)]) - case .prepaidStarsGiveaway(let id, let stars, let quantity, let date): - return ("prepaidStarsGiveaway", [("id", id as Any), ("stars", stars as Any), ("quantity", quantity as Any), ("date", date as Any)]) + case .prepaidStarsGiveaway(let id, let stars, let quantity, let boosts, let date): + return ("prepaidStarsGiveaway", [("id", id as Any), ("stars", stars as Any), ("quantity", quantity as Any), ("boosts", boosts as Any), ("date", date as Any)]) } } @@ -1076,12 +1077,15 @@ public extension Api { _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PrepaidGiveaway.prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, date: _4!) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PrepaidGiveaway.prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, boosts: _4!, date: _5!) } else { return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift index a93ad06a19..2a7f1bc106 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift @@ -91,7 +91,7 @@ public enum PremiumGiveawayInfo: Equatable { public struct PrepaidGiveaway: Equatable { public enum Prize: Equatable { case premium(months: Int32) - case stars(stars: Int64) + case stars(stars: Int64, boosts: Int32) } public let id: Int64 @@ -312,9 +312,9 @@ extension PrepaidGiveaway { self.prize = .premium(months: months) self.quantity = quantity self.date = date - case let .prepaidStarsGiveaway(id, stars, quantity, date): + case let .prepaidStarsGiveaway(id, stars, quantity, boosts, date): self.id = id - self.prize = .stars(stars: stars) + self.prize = .stars(stars: stars, boosts: boosts) self.quantity = quantity self.date = date } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 4ba983ce0b..5ec7990494 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -551,7 +551,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat } public func setPeer(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, peer: Peer, authorOfMessage: MessageReference?, emptyColor: UIColor) { - self.containerNode.isGestureEnabled = peer.smallProfileImage != nil + self.containerNode.isGestureEnabled = true var overrideImage: AvatarNodeImageOverride? if peer.isDeleted { diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/Contents.json new file mode 100644 index 0000000000..850bf80cff --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boostoff_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/boostoff_24.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/InstantViewOff.imageset/boostoff_24.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c2ad22e1d1b66b3ef84073482112b9f7e8d67f29 GIT binary patch literal 1789 zcmZXVdo+}J7{}YKIaA}(xaH2PXvt;nNQ}Eh?n9eY8qI5r%Zz4T2erl)mD||bNJWGx zH3-v4b)p-gVIx{u$vLBB+!8VCQtb>;ot{75^LxMN_gucud(P)I@^f)B$5>b)5EuXr zFv21c0AOni5QzwoPLaJLu;1z^H(E3VG69rZG#LV2z=I45D9w-t$_u1Zp$Gscy&fQy zq!m~Y6C}qV5`N<3wr5ia@@|qD)p(9enr?G;-5-rw38~Mr9C(@=IE~Y+f{?f)+zD8{ zxZ7jQx+rzKnYl1N6@?xicT*KlF&2qvFQfx4r^xPCh*Q z)OsS6-cuvVWJs#&B8HO8x2u-chG!27V`?#PGi(F5Yl}uty@EJ2_EmfljoX%!{6 z9Y>nFHNcPaCD}Lb>GAt>&_C9q?odmF_z~5f@3E0u=U0@CF%Ar9aF*mpkZ?hIhdY}& zwI2W4*cA#ayhKCXn_@9PzB2w=6!-SX?)}6gD&DR|D_Uo&&5No!QAt3q4d31sNigeAM7F5~k1&VFh>`|aor+GI(;(&c^!-Tr z4=?n}%m$UpYEJ+_`SyRR$CFw*y25>K)^WnGA1ZW4B&U0vZM7;F3m0x0c-tr;Hx(H5 zSC#Ycz=ogs2bq}uRBhyF9JGk~fF2$sNa761- zjI(8{bsD{7zmMU0fDKO^Xo;=&rdCw5ojB7ky%g#AFmDrqDXr9JCg^^t>u$w$x-lbu z%(+{%5F1Z`i!Jo#>Fdvk2WQo(K?xTNScu+^ewf1alg4eeHq6P8v$vcn&<`RYaO%w(pg6llSW4wC0P|?uOfL+TPZ| zo~Mo)D&1H==N#ldq>~#_>JEo(VEJD@4q&J`@l^(QG7h|3$kN1(3Rsj^(Vp>DB&Z&f zubtRK%(OP&mHOECaD|9lw97}JpBcsTzxcw{W>3*N`%R!lpIGsfz0ne5{3` z$j7fQr?_QWu4G5AS$EW3HQrnbEUjPHonwZW!P}LoBmGZZM}{g+l&IaYuz@wMu_KU; zwIsjwBGMgQ!}m@m2km5c9C`j%QSz;%`j4NI@Adx9pVe>8PjPgc;vF?utPkFwIUnBD z>R729*5SJEoD*33EPWA449!uw!0i8Z?Be_S6KiAI;5Ap_UG(RzR*zdOuUL$M<6fdH zObt6FPpIs)6=$v)>+;!l;WD5QX{aTEG%Hifspket%Y?$5#W}SO)53S1xaf|q!2J&n z%oB4mCQILrAqVt_?vD-&#|H;bCchR~EZh}}?CSz2H_J%%1vM+ol0j&hWf%+=y^1pY zsw_nu%8^cIKrA3+-xq)dCdPsQ%8yK4A(}Txp^=>!2|x&dwm>h%*8>*6B7HrrR0;rD zj5y{&kOjz~Dx;wckj@M`1k$A)Wkg*O{m4wI1`xhT-*8tZt@0SPlSYw_3dkG;04sf* z8FA8F%n$RkmifD~4a@H;$qceq8_EV-HttM@$k7by_o)d?FdTsftPsogWlzA8K)?|I zsr)4{Xkh6C&{rj#bWT~UN*FB8`UlBUx}EQD;eSjdpuZ+UOfoGRWFll=&%;H!4r3N0 v1~dVTX%v&?rJw>B3g)DD32W9j literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 25f96e9251..31c602eaee 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -306,11 +306,7 @@ public final class AccountContextImpl: AccountContext { self.account.stateManager.starsContext = self.starsContext - if let locationManager = self.sharedContextImpl.locationManager, sharedContext.applicationBindings.isMainApp && !temp { - self.peersNearbyManager = PeersNearbyManagerImpl(account: account, engine: self.engine, locationManager: locationManager, inForeground: sharedContext.applicationBindings.applicationInForeground) - } else { - self.peersNearbyManager = nil - } + self.peersNearbyManager = nil self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a45bbaae1d..ab21f3e8f6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -129,6 +129,7 @@ import PhoneNumberFormat import OwnershipTransferController import OldChannelsController import BrowserUI +import NotificationPeerExceptionController public enum ChatControllerPeekActions { case standard @@ -3617,13 +3618,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (dataSignal |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, message in - guard let strongSelf = self, let peer = peer, peer.smallProfileImage != nil else { + guard let strongSelf = self, let peer = peer else { return } - - let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in - }, synchronousLoad: true) - galleryController.setHintWillBePresentedInPreviewingContext(true) var isChannel = false if case let .channel(peer) = peer, case .broadcast = peer.info { @@ -3681,7 +3678,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.canReadHistory.set(false) - let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + let source: ContextContentSource + if let _ = peer.smallProfileImage { + let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in + }, synchronousLoad: true) + galleryController.setHintWillBePresentedInPreviewingContext(true) + source = .controller(ChatContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)) + } else { + source = .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: node.view, insets: .zero)) + } + + let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), gesture: gesture) contextController.dismissed = { [weak self] in self?.canReadHistory.set(true) } @@ -4655,58 +4662,347 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .peer, .replyThread: let avatarNode = ChatAvatarNavigationNode() avatarNode.contextAction = { [weak self] node, gesture in - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil else { + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer else { return } - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in - }, synchronousLoad: true) - galleryController.setHintWillBePresentedInPreviewingContext(true) - let items: Signal<[ContextMenuItem], NoError> = context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peer.id) - ) - |> map { canViewStats -> [ContextMenuItem] in - var items: [ContextMenuItem] = [ - .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, icon: { theme in + let items: Signal<[ContextMenuItem], NoError> + switch chatLocation { + case .peer: + items = context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peer.id) + ) + |> map { canViewStats -> [ContextMenuItem] in + var items: [ContextMenuItem] = [] + + let openText = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile + items.append(.action(ContextMenuActionItem(text: openText, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.dismissWithoutContent) self?.navigationButtonAction(.openChatInfo(expandAvatar: true, recommendedChannels: false)) - })) - ] - if canViewStats { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChannelInfo_Stats, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor) + }))) + + if canViewStats { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChannelInfo_Stats, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.dismissWithoutContent) + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer else { + return + } + strongSelf.view.endEditing(true) + + let statsController: ViewController + if let channel = peer as? TelegramChannel, case .group = channel.info { + statsController = groupStatsController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id) + } else { + statsController = channelStatsController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id) + } + strongSelf.push(statsController) + }))) + } + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.dismissWithoutContent) - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer else { - return - } - strongSelf.view.endEditing(true) - - let statsController: ViewController - if let channel = peer as? TelegramChannel, case .group = channel.info { - statsController = groupStatsController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id) - } else { - statsController = channelStatsController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id) - } - strongSelf.push(statsController) + self?.interfaceInteraction?.beginMessageSearch(.everything, "") }))) + + return items } - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.dismissWithoutContent) - self?.interfaceInteraction?.beginMessageSearch(.everything, "") - }))) - return items + case let .replyThread(message): + let peerId = peer.id + let threadId = message.threadId + + items = context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId), + TelegramEngine.EngineData.Item.Peer.ThreadData(id: peer.id, threadId: threadId), + TelegramEngine.EngineData.Item.NotificationSettings.Global() + ) + |> map { peerNotificationSettings, threadData, globalNotificationSettings -> [ContextMenuItem] in + guard let channel = peer as? TelegramChannel else { + return [] + } + guard let threadData = threadData else { + return [] + } + + var items: [ContextMenuItem] = [] + + var isMuted = false + switch threadData.notificationSettings.muteState { + case .muted: + isMuted = true + case .unmuted: + isMuted = false + case .default: + var peerIsMuted = false + if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } else if case .default = peerNotificationSettings.muteState { + if case .group = channel.info { + peerIsMuted = !globalNotificationSettings.groupChats.enabled + } + } + isMuted = peerIsMuted + } + + items.append(.action(ContextMenuActionItem(text: isMuted ? presentationData.strings.ChatList_Context_Unmute : presentationData.strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + if isMuted { + let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0) + |> deliverOnMainQueue).startStandalone(completed: { + f(.default) + }) + } else { + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteFor, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + var subItems: [ContextMenuItem] = [] + + let presetValues: [Int32] = [ + 1 * 60 * 60, + 8 * 60 * 60, + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60 + ] + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: presentationData.strings, value: value), icon: { _ in + return nil + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: value).startStandalone() + + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } + + subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in + return nil + }, action: { _, f in + f(.default) + +// if let chatListController = chatListController { +// openCustomMute(context: context, peerId: peerId, threadId: threadId, baseController: chatListController) +// } + }))) + + c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true) + }))) + + items.append(.separator) + + var isSoundEnabled = true + switch threadData.notificationSettings.messageSound { + case .none: + isSoundEnabled = false + default: + break + } + + if case .muted = threadData.notificationSettings.muteState { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: nil).startStandalone() + + let iconColor: UIColor = .white + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } else if !isSoundEnabled { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_EnableSound, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .default).startStandalone() + + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_DisableSound, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .none).startStandalone() + + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.NotificationSettings.Global() + ) + |> deliverOnMainQueue).startStandalone(next: { globalSettings in + let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue + } + + let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue + } + + let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { + peerId, displayPreviews in + return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue + } + + let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { + peerId, mute in + return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue + } + + let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { + peerId, hideSender in + return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue + } + + let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue + } + + let defaultSound: PeerMessageSound + + if case .broadcast = channel.info { + defaultSound = globalSettings.channels.sound._asMessageSound() + } else { + defaultSound = globalSettings.groupChats.sound._asMessageSound() + } + + let canRemove = false + + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in + let _ = (updatePeerSound(peerId, sound) + |> deliverOnMainQueue).startStandalone(next: { _ in + }) + }, updatePeerNotificationInterval: { [weak self] peerId, muteInterval in + let _ = (updatePeerNotificationInterval(peerId, muteInterval) + |> deliverOnMainQueue).startStandalone(next: { _ in + if let muteInterval = muteInterval, muteInterval == Int32.max { + let iconColor: UIColor = .white + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + } + }) + }, updatePeerDisplayPreviews: { peerId, displayPreviews in + let _ = (updatePeerDisplayPreviews(peerId, displayPreviews) + |> deliverOnMainQueue).startStandalone(next: { _ in + + }) + }, updatePeerStoriesMuted: { peerId, mute in + let _ = (updatePeerStoriesMuted(peerId, mute) + |> deliverOnMainQueue).startStandalone() + }, updatePeerStoriesHideSender: { peerId, hideSender in + let _ = (updatePeerStoriesHideSender(peerId, hideSender) + |> deliverOnMainQueue).startStandalone() + }, updatePeerStorySound: { peerId, sound in + let _ = (updatePeerStorySound(peerId, sound) + |> deliverOnMainQueue).startStandalone() + }, removePeerFromExceptions: { + }, modifiedPeer: { + }) + exceptionController.navigationPresentation = .modal + self?.push(exceptionController) + }) + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: Int32.max).startStandalone() + + let iconColor: UIColor = .white + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + + c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) + } + }))) + + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.dismissWithoutContent) + self?.interfaceInteraction?.beginMessageSearch(.everything, "") + }))) + + if threadId != 1 { + var canOpenClose = false + if channel.flags.contains(.isCreator) { + canOpenClose = true + } else if channel.hasPermission(.manageTopics) { + canOpenClose = true + } else if threadData.isOwnedByMe { + canOpenClose = true + } + if canOpenClose { + items.append(.action(ContextMenuActionItem(text: threadData.isClosed ? presentationData.strings.ChatList_Context_ReopenTopic : presentationData.strings.ChatList_Context_CloseTopic, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: threadData.isClosed ? "Chat/Context Menu/Play": "Chat/Context Menu/Pause"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + let _ = context.engine.peers.setForumChannelTopicClosed(id: peer.id, threadId: threadId, isClosed: !threadData.isClosed).startStandalone() + }))) + } +// if channel.hasPermission(.deleteAllMessages) { +// items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak chatListController] _, f in +// f(.default) +// +// chatListController?.deletePeerThread(peerId: peerId, threadId: threadId) +// }))) +// } + } + + return items + } + default: + items = .single([]) } strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() strongSelf.canReadHistory.set(false) - let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let source: ContextContentSource + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil { + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in + }, synchronousLoad: true) + galleryController.setHintWillBePresentedInPreviewingContext(true) + source = .controller(ChatContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)) + } else { + source = .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: node.view, insets: .zero)) + } + + let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) contextController.dismissed = { [weak self] in self?.canReadHistory.set(true) } @@ -5123,7 +5419,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G imageOverride = nil } (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride) - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = strongSelf.chatLocation.threadId == nil && peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile strongSelf.storyStats = peerView.storyStats @@ -5660,7 +5956,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId { self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - + let replyThreadType: ChatTitleContent.ReplyThreadType var replyThreadId: Int64? switch chatLocation { @@ -6013,6 +6309,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.isNodeLoaded { strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle } + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = true var peerDiscussionId: PeerId? var peerGeoLocation: PeerGeoLocation?