diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6b1c878a99..9fe1c6f923 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9197,6 +9197,8 @@ Sorry for the inconvenience."; "WebApp.LaunchMoreInfo" = "More about this bot"; "WebApp.LaunchConfirmation" = "To launch this web app, you will connect to its website."; +"WebApp.LaunchTermsConfirmation" = "By launching this mini app, you agree to the [Terms of Service for Mini Apps]()."; +"WebApp.LaunchTermsConfirmation_URL" = "https://telegram.org/tos/mini-apps"; "WebApp.LaunchOpenApp" = "Open App"; "WallpaperPreview.PreviewInNightMode" = "Preview this wallpaper in night mode."; @@ -12517,6 +12519,7 @@ Sorry for the inconvenience."; "Notification.StarsGift.Title_any" = "%@ Stars"; "Notification.StarsGift.Subtitle" = "Use Stars to unlock content and services on Telegram."; "Notification.StarsGift.SubtitleYou" = "With Stars, %@ will be able to unlock content and services on Telegram."; +"Notification.StarsGift.UnknownUser" = "Unknown user"; "Bot.Settings" = "Bot Settings"; @@ -12736,3 +12739,32 @@ Sorry for the inconvenience."; "Stars.Transfer.Subscribe.Successful.Text" = "%1$@ transferred to %2$@."; "Gallery.Ad" = "Ad"; + +"Chat.SensitiveContent" = "18+ Content"; + +"Settings.SensitiveContent" = "Show 18+ Content"; +"Settings.SensitiveContentInfo" = "Do not hide media that contain content suitable only for adults."; + +"SensitiveContent.Title" = "18+ Content"; +"SensitiveContent.Text" = "This media may contain sensitive content suitable only for adults.\nDo you still want to view it?"; +"SensitiveContent.ShowAlways" = "Always show 18+ media"; +"SensitiveContent.ViewAnyway" = "View Anyway"; +"SensitiveContent.SettingsInfo" = "You can update the visibility of sensitive media in [Data and Storage > Show 18+ Content]()."; + +"Notification.Refund" = "You received a refund of {amount} from {name}"; + +"InviteLink.SubscriptionFee.Title" = "SUBSCRIPTION FEE"; +"InviteLink.SubscriptionFee.PerMonth" = "%@ / month"; +"InviteLink.SubscriptionFee.NoOneJoined" = "No one joined yet"; +"InviteLink.SubscriptionFee.ApproximateIncome" = "You get approximately %@ monthly"; + +"InviteLink.PerMonth" = "per month"; + +"InviteLink.Create.Fee" = "Require Monthly Fee"; +"InviteLink.Create.FeePerMonth" = "%@ / month"; +"InviteLink.Create.FeePlaceholder" = "Stars amount per month"; +"InviteLink.Create.FeeInfo" = "Charge a subscription fee from people joining your channel via this link. [Learn More >]()"; +"InviteLink.Create.FeeEditInfo" = "If you need to change the subscription fee, create a new invite link with a different price."; +"InviteLink.Create.RequestApprovalFeeUnavailable" = "You can't enable admin approval for links that require a monthly fee."; + +"WebApp.PrivacyPolicy_URL" = "https://telegram.org/privacy-tpa"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index a3afd814b0..58145b36b1 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -996,6 +996,8 @@ public protocol SharedAccountContext: AnyObject { func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController + func makeDataAndStorageController(context: AccountContext, sensitiveContent: Bool) -> ViewController + func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController func makeChannelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, boosts: Bool, boostStatus: ChannelBoostStatus?) -> ViewController diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index a66a47da4d..6f97ed6086 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -62,6 +62,7 @@ public final class ChatMessageItemAssociatedData: Equatable { public let deviceContactsNumbers: Set public let isStandalone: Bool public let isInline: Bool + public let showSensitiveContent: Bool public init( automaticDownloadPeerType: MediaAutoDownloadPeerType, @@ -94,7 +95,8 @@ public final class ChatMessageItemAssociatedData: Equatable { chatThemes: [TelegramTheme] = [], deviceContactsNumbers: Set = Set(), isStandalone: Bool = false, - isInline: Bool = false + isInline: Bool = false, + showSensitiveContent: Bool = false ) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerId = automaticDownloadPeerId @@ -127,6 +129,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.deviceContactsNumbers = deviceContactsNumbers self.isStandalone = isStandalone self.isInline = isInline + self.showSensitiveContent = showSensitiveContent } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -217,6 +220,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.isInline != rhs.isInline { return false } + if lhs.showSensitiveContent != rhs.showSensitiveContent { + return false + } return true } } diff --git a/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift b/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift index 5e27921837..1ff599254f 100644 --- a/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift @@ -105,6 +105,7 @@ public class MediaDustNode: ASDisplayNode { private var staticNode: ASImageNode? private var staticParams: CGSize? + public var revealOnTap = true public var isRevealed = false private var isExploding = false @@ -218,6 +219,10 @@ public class MediaDustNode: ASDisplayNode { self.tapped() + guard self.revealOnTap else { + return + } + self.isRevealed = true if self.enableAnimations { diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift index 9a06e280ef..1d2a35b0c5 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift @@ -489,20 +489,19 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: let isEditingEnabled = invite?.pricing == nil let isSubscription = state.subscriptionEnabled if !isGroup { - //TODO:localize - entries.append(.subscriptionFeeToggle(presentationData.theme, "Require Monthly Fee", state.subscriptionEnabled, isEditingEnabled)) + entries.append(.subscriptionFeeToggle(presentationData.theme, presentationData.strings.InviteLink_Create_Fee, state.subscriptionEnabled, isEditingEnabled)) if state.subscriptionEnabled { var label: String = "" if let subscriptionFee = state.subscriptionFee, subscriptionFee > 0, let starsState { - label = "≈\(formatTonUsdValue(subscriptionFee, divide: false, rate: starsState.usdRate, dateTimeFormat: presentationData.dateTimeFormat)) / month" + label = presentationData.strings.InviteLink_Create_FeePerMonth("≈\(formatTonUsdValue(subscriptionFee, divide: false, rate: starsState.usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string } - entries.append(.subscriptionFee(presentationData.theme, "Stars amount per month", isEditingEnabled, state.subscriptionFee, label, configuration.maxFee)) + entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, configuration.maxFee)) } let infoText: String if let _ = invite, state.subscriptionEnabled { - infoText = "If you need to change the subscription fee, create a new invite link with a different price." + infoText = presentationData.strings.InviteLink_Create_FeeEditInfo } else { - infoText = "Charge a subscription fee from people joining your channel via this link. [Learn More >]()" + infoText = presentationData.strings.InviteLink_Create_FeeInfo } entries.append(.subscriptionFeeInfo(presentationData.theme, infoText)) } @@ -511,7 +510,7 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: entries.append(.requestApproval(presentationData.theme, presentationData.strings.InviteLink_Create_RequestApproval, state.requestApproval, isEditingEnabled && !isSubscription)) var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel if isSubscription { - requestApprovalInfoText = "You can't enable admin approval for links that require a monthly fee." + requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalFeeUnavailable } else { if state.requestApproval { requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOnInfoChannel diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index 2ffa88c3f8..fac7a302e2 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -310,10 +310,9 @@ private enum InviteLinkViewEntry: Comparable, Identifiable { let label: ItemListPeerItemLabel if let pricing { - //TODO:localize let text = NSMutableAttributedString() text.append(NSAttributedString(string: "⭐️\(pricing.amount)\n", font: Font.semibold(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) - text.append(NSAttributedString(string: "per month", font: Font.regular(13.0), textColor: presentationData.theme.list.itemSecondaryTextColor)) + text.append(NSAttributedString(string: presentationData.strings.InviteLink_PerMonth, font: Font.regular(13.0), textColor: presentationData.theme.list.itemSecondaryTextColor)) if let range = text.string.range(of: "⭐️") { text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: text.string)) text.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(range, in: text.string)) @@ -841,15 +840,14 @@ public final class InviteLinkViewController: ViewController { entries.append(.link(presentationData.theme, invite)) if let pricing = invite.pricing { - //TODO:localize - entries.append(.subscriptionHeader(presentationData.theme, "SUBSCRIPTION FEE")) - var title = "⭐️\(pricing.amount) / month" - var subtitle = "No one joined yet" + entries.append(.subscriptionHeader(presentationData.theme, presentationData.strings.InviteLink_SubscriptionFee_Title.uppercased())) + var title = presentationData.strings.InviteLink_SubscriptionFee_PerMonth("\(pricing.amount)").string + var subtitle = presentationData.strings.InviteLink_SubscriptionFee_NoOneJoined if state.count > 0 { title += " x \(state.count)" if let usdRate { let usdValue = formatTonUsdValue(pricing.amount * Int64(state.count), divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat) - subtitle = "You get approximately \(usdValue) monthly" + subtitle = presentationData.strings.InviteLink_SubscriptionFee_ApproximateIncome(usdValue).string } else { subtitle = "" } diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift index 3cb8afafa5..c1c9a6e3cb 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift @@ -404,10 +404,9 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { } if let subscriptionPricing { - //TODO:localize let text = NSMutableAttributedString() text.append(NSAttributedString(string: "⭐️\(subscriptionPricing.amount)\n", font: Font.semibold(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)) - text.append(NSAttributedString(string: "per month", font: Font.regular(13.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor)) + text.append(NSAttributedString(string: item.presentationData.strings.InviteLink_PerMonth, font: Font.regular(13.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor)) if let range = text.string.range(of: "⭐️") { text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: text.string)) text.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(range, in: text.string)) diff --git a/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift b/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift index ca8c03185b..abb4d86c6c 100644 --- a/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift +++ b/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift @@ -20,6 +20,9 @@ public extension Message { public extension RestrictedContentMessageAttribute { func platformText(platform: String, contentSettings: ContentSettings) -> String? { for rule in self.rules { + if rule.isSensitive { + continue + } if rule.platform == "all" || rule.platform == "ios" || contentSettings.addContentRestrictionReasons.contains(rule.platform) { if !contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) { return rule.text diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index f220088e4d..a32b6c972e 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -232,6 +232,12 @@ public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal, _ s14: Signal, _ s15: Signal, _ s16: Signal, _ s17: Signal, _ s18: Signal, _ s19: Signal, _ s20: Signal, _ s21: Signal, _ s22: Signal, _ s23: Signal, _ s24: Signal, _ s25: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19), signalOfAny(s20), signalOfAny(s21), signalOfAny(s22), signalOfAny(s23), signalOfAny(s24), signalOfAny(s25)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19, values[19] as! T20, values[20] as! T21, values[21] as! T22, values[22] as! T23, values[23] as! T24, values[24] as! T25) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift index adf77392b4..72496f4c82 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift @@ -34,9 +34,26 @@ private final class DataAndStorageControllerArguments { let toggleDownloadInBackground: (Bool) -> Void let openBrowserSelection: () -> Void let openIntents: () -> Void - let toggleEnableSensitiveContent: (Bool) -> Void + let toggleSensitiveContent: (Bool) -> Void + let toggleOtherSensitiveContent: (Bool) -> Void - init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, togglePauseMusicOnRecording: @escaping (Bool) -> Void, toggleRaiseToListen: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) { + init( + openStorageUsage: @escaping () -> Void, + openNetworkUsage: @escaping () -> Void, + openProxy: @escaping () -> Void, + openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, + resetAutomaticDownload: @escaping () -> Void, + toggleVoiceUseLessData: @escaping (Bool) -> Void, + openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void, + toggleSaveEditedPhotos: @escaping (Bool) -> Void, + togglePauseMusicOnRecording: @escaping (Bool) -> Void, + toggleRaiseToListen: @escaping (Bool) -> Void, + toggleDownloadInBackground: @escaping (Bool) -> Void, + openBrowserSelection: @escaping () -> Void, + openIntents: @escaping () -> Void, + toggleSensitiveContent: @escaping (Bool) -> Void, + toggleOtherSensitiveContent: @escaping (Bool) -> Void + ) { self.openStorageUsage = openStorageUsage self.openNetworkUsage = openNetworkUsage self.openProxy = openProxy @@ -50,7 +67,8 @@ private final class DataAndStorageControllerArguments { self.toggleDownloadInBackground = toggleDownloadInBackground self.openBrowserSelection = openBrowserSelection self.openIntents = openIntents - self.toggleEnableSensitiveContent = toggleEnableSensitiveContent + self.toggleSensitiveContent = toggleSensitiveContent + self.toggleOtherSensitiveContent = toggleOtherSensitiveContent } } @@ -62,7 +80,8 @@ private enum DataAndStorageSection: Int32 { case voiceCalls case other case connection - case enableSensitiveContent + case sensitiveContent + case otherSensitiveContent } public enum DataAndStorageEntryTag: ItemListItemTag, Equatable { @@ -72,6 +91,7 @@ public enum DataAndStorageEntryTag: ItemListItemTag, Equatable { case pauseMusicOnRecording case raiseToListen case autoSave(AutomaticSaveIncomingPeerType) + case sensitiveContent public func isEqual(to other: ItemListItemTag) -> Bool { if let other = other as? DataAndStorageEntryTag, self == other { @@ -107,9 +127,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case raiseToListen(PresentationTheme, String, Bool) case raiseToListenInfo(PresentationTheme, String) + case sensitiveContent(String, Bool) + case sensitiveContentInfo(String) + case connectionHeader(PresentationTheme, String) case connectionProxy(PresentationTheme, String, String) - case enableSensitiveContent(String, Bool) + case otherSensitiveContent(String, Bool) var section: ItemListSectionId { switch self { @@ -125,10 +148,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return DataAndStorageSection.voiceCalls.rawValue case .otherHeader, .openLinksIn, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo: return DataAndStorageSection.other.rawValue + case .sensitiveContent, .sensitiveContentInfo: + return DataAndStorageSection.sensitiveContent.rawValue case .connectionHeader, .connectionProxy: return DataAndStorageSection.connection.rawValue - case .enableSensitiveContent: - return DataAndStorageSection.enableSensitiveContent.rawValue + case .otherSensitiveContent: + return DataAndStorageSection.otherSensitiveContent.rawValue } } @@ -174,12 +199,16 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return 34 case .raiseToListenInfo: return 35 - case .connectionHeader: + case .sensitiveContent: return 36 - case .connectionProxy: + case .sensitiveContentInfo: return 37 - case .enableSensitiveContent: + case .connectionHeader: return 38 + case .connectionProxy: + return 39 + case .otherSensitiveContent: + return 40 } } @@ -293,6 +322,18 @@ private enum DataAndStorageEntry: ItemListNodeEntry { } else { return false } + case let .sensitiveContent(text, value): + if case .sensitiveContent(text, value) = rhs { + return true + } else { + return false + } + case let .sensitiveContentInfo(text): + if case .sensitiveContentInfo(text) = rhs { + return true + } else { + return false + } case let .downloadInBackground(lhsTheme, lhsText, lhsValue): if case let .downloadInBackground(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -317,8 +358,8 @@ private enum DataAndStorageEntry: ItemListNodeEntry { } else { return false } - case let .enableSensitiveContent(text, value): - if case .enableSensitiveContent(text, value) = rhs { + case let .otherSensitiveContent(text, value): + if case .otherSensitiveContent(text, value) = rhs { return true } else { return false @@ -408,6 +449,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry { }, tag: DataAndStorageEntryTag.raiseToListen) case let .raiseToListenInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) + case let .sensitiveContent(text, value): + return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggleSensitiveContent(value) + }, tag: DataAndStorageEntryTag.sensitiveContent) + case let .sensitiveContentInfo(text): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .downloadInBackground(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleDownloadInBackground(value) @@ -420,9 +467,9 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openProxy() }) - case let .enableSensitiveContent(text, value): + case let .otherSensitiveContent(text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in - arguments.toggleEnableSensitiveContent(value) + arguments.toggleOtherSensitiveContent(value) }, tag: nil) } } @@ -588,7 +635,7 @@ private func autosaveLabelAndValue(presentationData: PresentationData, settings: return (label, value) } -private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?]) -> [DataAndStorageEntry] { +private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings) -> [DataAndStorageEntry] { var entries: [DataAndStorageEntry] = [] entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData)))) @@ -627,6 +674,9 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat entries.append(.raiseToListen(presentationData.theme, presentationData.strings.Settings_RaiseToListen, data.mediaInputSettings.enableRaiseToSpeak)) entries.append(.raiseToListenInfo(presentationData.theme, presentationData.strings.Settings_RaiseToListenInfo)) + entries.append(.sensitiveContent(presentationData.strings.Settings_SensitiveContent, mediaSettings.showSensitiveContent)) + entries.append(.sensitiveContentInfo(presentationData.strings.Settings_SensitiveContentInfo)) + let proxyValue: String if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled { switch activeServer.connection { @@ -643,7 +693,7 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat #if DEBUG if let contentSettingsConfiguration = contentSettingsConfiguration, contentSettingsConfiguration.canAdjustSensitiveContent { - entries.append(.enableSensitiveContent("Display Sensitive Content", contentSettingsConfiguration.sensitiveContentEnabled)) + entries.append(.otherSensitiveContent("Display Sensitive Content", contentSettingsConfiguration.sensitiveContentEnabled)) } #endif @@ -872,7 +922,11 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da }, openIntents: { let controller = intentsSettingsController(context: context) pushControllerImpl?(controller) - }, toggleEnableSensitiveContent: { value in + }, toggleSensitiveContent: { value in + let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { + $0.withUpdatedShowSensitiveContent(value) + }).startStandalone() + }, toggleOtherSensitiveContent: { value in let _ = (contentSettingsConfiguration.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak contentSettingsConfiguration] settings in @@ -905,7 +959,7 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da context.sharedContext.presentationData, statePromise.get(), dataAndStorageDataPromise.get(), - context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]), + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), contentSettingsConfiguration.get(), preferences, usageSignal, @@ -913,6 +967,8 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da ) |> map { presentationData, state, dataAndStorageData, sharedData, contentSettingsConfiguration, mediaAutoSaveSettings, usageSignal, autosaveExceptionPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in let webBrowserSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings + let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings + let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org")) let defaultWebBrowser: String if let option = options.first(where: { $0.identifier == webBrowserSettings.defaultWebBrowser }) { @@ -924,7 +980,7 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index b1a774dc68..c1aacde520 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -275,7 +275,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) } dict[-317144808] = { return Api.EncryptedMessage.parse_encryptedMessage($0) } dict[594758406] = { return Api.EncryptedMessage.parse_encryptedMessageService($0) } - dict[-1812799720] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } + dict[-1574126186] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } dict[-317687113] = { return Api.ExportedChatInvite.parse_chatInvitePublicJoinRequests($0) } dict[206668204] = { return Api.ExportedChatlistInvite.parse_exportedChatlistInvite($0) } dict[1103040667] = { return Api.ExportedContactToken.parse_exportedContactToken($0) } @@ -803,6 +803,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1929860175] = { return Api.RequestedPeer.parse_requestedPeerChat($0) } dict[-701500310] = { return Api.RequestedPeer.parse_requestedPeerUser($0) } dict[-797791052] = { return Api.RestrictionReason.parse_restrictionReason($0) } + dict[2007669447] = { return Api.RestrictionReason.parse_restrictionReasonSensitive($0) } dict[894777186] = { return Api.RichText.parse_textAnchor($0) } dict[1730456516] = { return Api.RichText.parse_textBold($0) } dict[2120376535] = { return Api.RichText.parse_textConcat($0) } diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 73eedd5544..f7bb2d7515 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -397,6 +397,7 @@ public extension Api { public extension Api { enum RestrictionReason: TypeConstructorDescription { case restrictionReason(platform: String, reason: String, text: String) + case restrictionReasonSensitive(platform: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -408,6 +409,12 @@ public extension Api { serializeString(reason, buffer: buffer, boxed: false) serializeString(text, buffer: buffer, boxed: false) break + case .restrictionReasonSensitive(let platform): + if boxed { + buffer.appendInt32(2007669447) + } + serializeString(platform, buffer: buffer, boxed: false) + break } } @@ -415,6 +422,8 @@ public extension Api { switch self { case .restrictionReason(let platform, let reason, let text): return ("restrictionReason", [("platform", platform as Any), ("reason", reason as Any), ("text", text as Any)]) + case .restrictionReasonSensitive(let platform): + return ("restrictionReasonSensitive", [("platform", platform as Any)]) } } @@ -435,6 +444,17 @@ public extension Api { return nil } } + public static func parse_restrictionReasonSensitive(_ reader: BufferReader) -> RestrictionReason? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RestrictionReason.restrictionReasonSensitive(platform: _1!) + } + else { + return nil + } + } } } diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 3074843dc8..21cb33978a 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -7935,14 +7935,15 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendPaidReaction(peer: Api.InputPeer, msgId: Int32, count: Int32, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendPaidReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, count: Int32, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(508941107) + buffer.appendInt32(633929278) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) serializeInt64(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendPaidReaction", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("count", String(describing: count)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.sendPaidReaction", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("count", String(describing: count)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index f5894fb213..aae395e494 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -1012,14 +1012,14 @@ public extension Api { } public extension Api { enum ExportedChatInvite: TypeConstructorDescription { - case chatInviteExported(flags: Int32, link: String, adminId: Int64, date: Int32, startDate: Int32?, expireDate: Int32?, usageLimit: Int32?, usage: Int32?, requested: Int32?, title: String?, subscriptionPricing: Api.StarsSubscriptionPricing?) + case chatInviteExported(flags: Int32, link: String, adminId: Int64, date: Int32, startDate: Int32?, expireDate: Int32?, usageLimit: Int32?, usage: Int32?, requested: Int32?, subscriptionExpired: Int32?, title: String?, subscriptionPricing: Api.StarsSubscriptionPricing?) case chatInvitePublicJoinRequests public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage, let requested, let title, let subscriptionPricing): + case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage, let requested, let subscriptionExpired, let title, let subscriptionPricing): if boxed { - buffer.appendInt32(-1812799720) + buffer.appendInt32(-1574126186) } serializeInt32(flags, buffer: buffer, boxed: false) serializeString(link, buffer: buffer, boxed: false) @@ -1030,6 +1030,7 @@ public extension Api { if Int(flags) & Int(1 << 2) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(usage!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 7) != 0 {serializeInt32(requested!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(subscriptionExpired!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {subscriptionPricing!.serialize(buffer, true)} break @@ -1044,8 +1045,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage, let requested, let title, let subscriptionPricing): - return ("chatInviteExported", [("flags", flags as Any), ("link", link as Any), ("adminId", adminId as Any), ("date", date as Any), ("startDate", startDate as Any), ("expireDate", expireDate as Any), ("usageLimit", usageLimit as Any), ("usage", usage as Any), ("requested", requested as Any), ("title", title as Any), ("subscriptionPricing", subscriptionPricing as Any)]) + case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage, let requested, let subscriptionExpired, let title, let subscriptionPricing): + return ("chatInviteExported", [("flags", flags as Any), ("link", link as Any), ("adminId", adminId as Any), ("date", date as Any), ("startDate", startDate as Any), ("expireDate", expireDate as Any), ("usageLimit", usageLimit as Any), ("usage", usage as Any), ("requested", requested as Any), ("subscriptionExpired", subscriptionExpired as Any), ("title", title as Any), ("subscriptionPricing", subscriptionPricing as Any)]) case .chatInvitePublicJoinRequests: return ("chatInvitePublicJoinRequests", []) } @@ -1070,11 +1071,13 @@ public extension Api { if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt32() } var _9: Int32? if Int(_1!) & Int(1 << 7) != 0 {_9 = reader.readInt32() } - var _10: String? - if Int(_1!) & Int(1 << 8) != 0 {_10 = parseString(reader) } - var _11: Api.StarsSubscriptionPricing? + var _10: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_10 = reader.readInt32() } + var _11: String? + if Int(_1!) & Int(1 << 8) != 0 {_11 = parseString(reader) } + var _12: Api.StarsSubscriptionPricing? if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing + _12 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing } } let _c1 = _1 != nil let _c2 = _2 != nil @@ -1085,10 +1088,11 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 7) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.ExportedChatInvite.chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8, requested: _9, title: _10, subscriptionPricing: _11) + let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 9) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.ExportedChatInvite.chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8, requested: _9, subscriptionExpired: _10, title: _11, subscriptionPricing: _12) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift b/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift index 67e92ed228..e358457637 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift @@ -6,7 +6,8 @@ import TelegramApi extension ExportedInvitation { init(apiExportedInvite: Api.ExportedChatInvite) { switch apiExportedInvite { - case let .chatInviteExported(flags, link, adminId, date, startDate, expireDate, usageLimit, usage, requested, title, pricing): + case let .chatInviteExported(flags, link, adminId, date, startDate, expireDate, usageLimit, usage, requested, subscriptionExpired, title, pricing): + let _ = subscriptionExpired self = .link(link: link, title: title, isPermanent: (flags & (1 << 5)) != 0, requestApproval: (flags & (1 << 6)) != 0, isRevoked: (flags & (1 << 0)) != 0, adminId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), date: date, startDate: startDate, expireDate: expireDate, usageLimit: usageLimit, count: usage, requestedCount: requested, pricing: pricing.flatMap { StarsSubscriptionPricing(apiStarsSubscriptionPricing: $0) }) case .chatInvitePublicJoinRequests: self = .publicJoinRequest diff --git a/submodules/TelegramCore/Sources/ApiUtils/PeerAccessRestrictionInfo.swift b/submodules/TelegramCore/Sources/ApiUtils/PeerAccessRestrictionInfo.swift index ec42c80376..69c164dcab 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/PeerAccessRestrictionInfo.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/PeerAccessRestrictionInfo.swift @@ -8,6 +8,8 @@ extension RestrictionRule { switch apiReason { case let .restrictionReason(platform, reason, text): self.init(platform: platform, reason: reason, text: text) + case let .restrictionReasonSensitive(platform): + self.init(platform: platform) } } } diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 2ca1488b74..7e458b2cb4 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -341,7 +341,7 @@ private func requestSendStarsReaction(postbox: Postbox, network: Network, stateM let timestampPart = UInt64(UInt32(bitPattern: Int32(Date().timeIntervalSince1970))) let randomId = (timestampPart << 32) | randomPartId - let signal: Signal = network.request(Api.functions.messages.sendPaidReaction(peer: inputPeer, msgId: messageId.id, count: count, randomId: Int64(bitPattern: randomId))) + let signal: Signal = network.request(Api.functions.messages.sendPaidReaction(flags: 0, peer: inputPeer, msgId: messageId.id, count: count, randomId: Int64(bitPattern: randomId))) |> mapError { _ -> RequestUpdateMessageReactionError in return .generic } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerAccessRestrictionInfo.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerAccessRestrictionInfo.swift index 940640e28f..a7de5ef3ee 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerAccessRestrictionInfo.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerAccessRestrictionInfo.swift @@ -4,23 +4,34 @@ public final class RestrictionRule: PostboxCoding, Equatable { public let platform: String public let reason: String public let text: String + public let isSensitive: Bool public init(platform: String, reason: String, text: String) { self.platform = platform self.reason = reason self.text = text + self.isSensitive = false + } + + public init(platform: String) { + self.platform = platform + self.reason = "" + self.text = "" + self.isSensitive = true } public init(decoder: PostboxDecoder) { self.platform = decoder.decodeStringForKey("p", orElse: "all") self.reason = decoder.decodeStringForKey("r", orElse: "") self.text = decoder.decodeStringForKey("t", orElse: "") + self.isSensitive = decoder.decodeBoolForKey("s", orElse: false) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.platform, forKey: "p") encoder.encodeString(self.reason, forKey: "r") encoder.encodeString(self.text, forKey: "t") + encoder.encodeBool(self.isSensitive, forKey: "s") } public static func ==(lhs: RestrictionRule, rhs: RestrictionRule) -> Bool { @@ -33,6 +44,9 @@ public final class RestrictionRule: PostboxCoding, Equatable { if lhs.text != rhs.text { return false } + if lhs.isSensitive != rhs.isSensitive { + return false + } return true } } diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 58903e0d0e..ae74ebe5cb 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -381,7 +381,15 @@ public extension Message { } } - func isAgeRestricted() -> Bool { + func isSensitiveContent(platform: String) -> Bool { + if let rule = self.restrictedContentAttribute?.rules.first(where: { $0.isSensitive }) { + if rule.platform == "all" || rule.platform == platform { + return true + } + } + if let peer = self.peers[self.id.peerId], peer.hasSensitiveContent(platform: platform) { + return true + } return false } } diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index 99b15c30c6..8357dc2e8a 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -30,6 +30,9 @@ public extension Peer { if let restrictionInfo = restrictionInfo { for rule in restrictionInfo.rules { + if rule.isSensitive { + continue + } if rule.platform == "all" || rule.platform == platform || contentSettings.addContentRestrictionReasons.contains(rule.platform) { if !contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) { return rule.text @@ -220,6 +223,25 @@ public extension Peer { } } + func hasSensitiveContent(platform: String) -> Bool { + var restrictionInfo: PeerAccessRestrictionInfo? + switch self { + case let user as TelegramUser: + restrictionInfo = user.restrictionInfo + case let channel as TelegramChannel: + restrictionInfo = channel.restrictionInfo + default: + break + } + + if let restrictionInfo, let rule = restrictionInfo.rules.first(where: { $0.isSensitive }) { + if rule.platform == "all" || rule.platform == platform { + return true + } + } + return false + } + var isForum: Bool { if let channel = self as? TelegramChannel { return channel.flags.contains(.isForum) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 1816f40ef8..4bdc13490f 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -751,11 +751,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, if message.author?.id == accountPeerId { attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentYou(price)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) } else { - //TODO:localize var authorName = compactAuthorName var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)] if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 { - authorName = "Unknown user" + authorName = strings.Notification_StarsGift_UnknownUser peerIds = [] } var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) @@ -1010,13 +1009,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } case let .paymentRefunded(peerId, currency, totalAmount, _, _): - //TODO:localize - let patternString: String - if peerId == message.id.peerId { - patternString = "You received a refund of {amount}" - } else { - patternString = "You received a refund of {amount} from {name}" - } + let patternString = strings.Notification_Refund let mutableString = NSMutableAttributedString() mutableString.append(NSAttributedString(string: patternString, font: titleFont, textColor: primaryTextColor)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 6bee292fad..20674b45fb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -217,7 +217,7 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { private let context: AccountContext private let blurredImageNode: TransformImageNode - private let dustNode: MediaDustNode + fileprivate let dustNode: MediaDustNode fileprivate let buttonNode: HighlightTrackingButtonNode private let highlightedBackgroundNode: ASDisplayNode private let iconNode: ASImageNode @@ -452,6 +452,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr private var automaticDownload: InteractiveMediaNodeAutodownloadMode? public var automaticPlayback: Bool? private var preferredStoryHighQuality: Bool = false + private var showSensitiveContent: Bool = false private let statusDisposable = MetaDisposable() private let fetchControls = Atomic(value: nil) @@ -1570,6 +1571,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr strongSelf.automaticPlayback = automaticPlayback strongSelf.automaticDownload = automaticDownload strongSelf.preferredStoryHighQuality = associatedData.preferredStoryHighQuality + strongSelf.showSensitiveContent = associatedData.showSensitiveContent if let previousArguments = strongSelf.currentImageArguments { if previousArguments.imageSize == arguments.imageSize { @@ -2399,20 +2401,25 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr displaySpoiler = true } else if isSecretMedia { displaySpoiler = true - } else if message.isAgeRestricted() { - displaySpoiler = true - icon = .eye + } else if message.isSensitiveContent(platform: "ios") { + if !self.showSensitiveContent { + displaySpoiler = true + icon = .eye + } } - if displaySpoiler { - if self.extendedMediaOverlayNode == nil, let context = self.context { + if displaySpoiler, let context = self.context { + let extendedMediaOverlayNode: ExtendedMediaOverlayNode + if let current = self.extendedMediaOverlayNode { + extendedMediaOverlayNode = current + } else { let enableAnimations = context.sharedContext.energyUsageSettings.fullTranslucency && !isPreview - let extendedMediaOverlayNode = ExtendedMediaOverlayNode(context: context, hasImageOverlay: !isSecretMedia, icon: icon, enableAnimations: enableAnimations) + extendedMediaOverlayNode = ExtendedMediaOverlayNode(context: context, hasImageOverlay: !isSecretMedia, icon: icon, enableAnimations: enableAnimations) extendedMediaOverlayNode.tapped = { [weak self] in guard let self else { return } - if message.isAgeRestricted() { + if message.isSensitiveContent(platform: "ios") { self.activateAgeRestrictedMedia?() } else { self.internallyVisible = true @@ -2423,7 +2430,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr self.extendedMediaOverlayNode = extendedMediaOverlayNode self.pinchContainerNode.contentNode.insertSubnode(extendedMediaOverlayNode, aboveSubnode: self.imageNode) } - self.extendedMediaOverlayNode?.frame = self.imageNode.frame + extendedMediaOverlayNode.frame = self.imageNode.frame var tappable = false if !isSecretMedia { @@ -2434,13 +2441,12 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr break } } - - self.extendedMediaOverlayNode?.isUserInteractionEnabled = tappable + extendedMediaOverlayNode.isUserInteractionEnabled = tappable var viewText: String = "" - if message.isAgeRestricted() { - //TODO:localize - viewText = "18+ Content" + if case .eye = icon { + viewText = strings.Chat_SensitiveContent + extendedMediaOverlayNode.dustNode.revealOnTap = false } else { outer: for attribute in message.attributes { if let attribute = attribute as? ReplyMarkupMessageAttribute { @@ -2455,8 +2461,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr break } } + extendedMediaOverlayNode.dustNode.revealOnTap = true } - self.extendedMediaOverlayNode?.update(size: self.imageNode.frame.size, text: viewText, imageSignal: self.currentBlurredImageSignal, imageFrame: self.imageNode.view.convert(self.imageNode.bounds, to: self.extendedMediaOverlayNode?.view), corners: self.currentImageArguments?.corners) + extendedMediaOverlayNode.update(size: self.imageNode.frame.size, text: viewText, imageSignal: self.currentBlurredImageSignal, imageFrame: self.imageNode.view.convert(self.imageNode.bounds, to: extendedMediaOverlayNode.view), corners: self.currentImageArguments?.corners) } else if let extendedMediaOverlayNode = self.extendedMediaOverlayNode { self.extendedMediaOverlayNode = nil extendedMediaOverlayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak extendedMediaOverlayNode] _ in @@ -2664,12 +2671,12 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } public func ignoreTapActionAtPoint(_ point: CGPoint) -> Bool { -// if let extendedMediaOverlayNode = self.extendedMediaOverlayNode { -// let convertedPoint = self.view.convert(point, to: extendedMediaOverlayNode.view) -// if extendedMediaOverlayNode.buttonNode.frame.contains(convertedPoint) { -// return true -// } -// } + if let extendedMediaOverlayNode = self.extendedMediaOverlayNode { + let convertedPoint = self.view.convert(point, to: extendedMediaOverlayNode.view) + if extendedMediaOverlayNode.buttonNode.frame.contains(convertedPoint) { + return true + } + } return false } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index a52f158bc7..dc6825564e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -1135,7 +1135,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { theme: forceDark ? .dark : .default ) - self.navigationPresentation = .flatModal + self.navigationPresentation = .standaloneModal self.automaticallyControlPresentationContextLayout = false openPeerImpl = { [weak self] peer in diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 39ff8d8111..5fe5a816ae 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -626,7 +626,6 @@ final class StarsTransactionsScreenComponent: Component { maximumNumberOfLines: 1 ))) ) - //TODO:localize let dateText: String let dateValue = stringForDateWithoutYear(date: Date(timeIntervalSince1970: Double(subscription.untilDate)), strings: environment.strings) if subscription.flags.contains(.isCancelled) { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 30d333d5ca..6c04340737 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -77,6 +77,7 @@ private final class SheetContent: CombinedComponent { private(set) var botPeer: EnginePeer? private(set) var chatPeer: EnginePeer? + private(set) var authorPeer: EnginePeer? private var peerDisposable: Disposable? private(set) var balance: Int64? private(set) var form: BotPaymentForm? @@ -799,7 +800,7 @@ public final class StarsTransferScreen: ViewControllerComponentContainer { theme: .default ) - self.navigationPresentation = .flatModal + self.navigationPresentation = .standaloneModal starsContext.load(force: false) } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 7a03e2b91c..ac660de671 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -524,7 +524,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { theme: .default ) - self.navigationPresentation = .flatModal + self.navigationPresentation = .standaloneModal } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index a39806ddb5..7f3e18ec47 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -294,7 +294,9 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() openWebView() - }, showMore: nil) + }, showMore: nil, openTerms: { + + }) parentController.present(controller, in: .window(.root)) } }) @@ -312,38 +314,38 @@ public extension ChatControllerImpl { } static func botRequestSwitchInline(context: AccountContext, controller: ChatControllerImpl?, peerId: EnginePeer.Id, botAddress: String, query: String, chatTypes: [ReplyMarkupButtonRequestPeerType]?, completion: @escaping () -> Void) -> Void { - let activateSwitchInline = { - var chatController: ChatControllerImpl? - if let current = controller { - chatController = current - } else if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController { - for controller in navigationController.viewControllers.reversed() { - if let controller = controller as? ChatControllerImpl { - chatController = controller - break - } + let activateSwitchInline: (EnginePeer?) -> Void = { selectedPeer in + var chatController: ChatControllerImpl? + if let current = controller { + chatController = current + } else if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController { + for controller in navigationController.viewControllers.reversed() { + if let controller = controller as? ChatControllerImpl { + chatController = controller + break } } - if let chatController { - chatController.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil) - } } - - if let chatTypes { - let peerController = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false)) - peerController.peerSelected = { [weak peerController] peer, _ in - completion() - peerController?.dismiss() - activateSwitchInline() - } - if let controller { - controller.push(peerController) - } else { - ((context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface)?.viewControllers.last as? ViewController)?.push(peerController) - } + if let chatController { + chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, "@\(botAddress) \(query)", nil) + } + } + + if let chatTypes { + let peerController = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false)) + peerController.peerSelected = { [weak peerController] peer, _ in + completion() + peerController?.dismiss() + activateSwitchInline(peer) + } + if let controller { + controller.push(peerController) } else { - activateSwitchInline() + ((context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface)?.viewControllers.last as? ViewController)?.push(peerController) } + } else { + activateSwitchInline(nil) + } } private static func botOpenPeer(context: AccountContext, peerId: EnginePeer.Id, navigation: ChatControllerInteractionNavigateToPeer, navigationController: NavigationController) { @@ -547,6 +549,10 @@ public extension ChatControllerImpl { if let self { self.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil) } + }, openTerms: { [weak self] in + if let self { + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: self.presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: self.presentationData, navigationController: self.effectiveNavigationController, dismissInput: {}) + } }) self.present(controller, in: .window(.root)) } diff --git a/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift b/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift index b1cfa2f40b..aab9ba81e9 100644 --- a/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift +++ b/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift @@ -128,7 +128,7 @@ private final class ChatAgeRestrictionAlertContentNode: AlertContentNode { self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.alwaysLabelNode.attributedText = formattedText("Always show 18+ media", color: theme.primaryColor) + self.alwaysLabelNode.attributedText = formattedText(self.strings.SensitiveContent_ShowAlways, color: theme.primaryColor) self.actionNodesSeparator.backgroundColor = theme.separatorColor for actionNode in self.actionNodes { @@ -269,10 +269,9 @@ public func chatAgeRestrictionAlertController(context: AccountContext, updatedPr } let strings = presentationData.strings - //TODO:localize var dismissImpl: ((Bool) -> Void)? var getContentNodeImpl: (() -> ChatAgeRestrictionAlertContentNode?)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: "View Anyway", action: { + let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: strings.SensitiveContent_ViewAnyway, action: { if let alwaysShow = getContentNodeImpl?()?.alwaysShow { completion(alwaysShow) } else { @@ -283,8 +282,8 @@ public func chatAgeRestrictionAlertController(context: AccountContext, updatedPr dismissImpl?(true) })] - let title = "18+ Content" - let text = "This media may contain sensitive content suitable only for adults.\nDo you still want to view it?" + let title = strings.SensitiveContent_Title + let text = strings.SensitiveContent_Text let contentNode = ChatAgeRestrictionAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, actions: actions) getContentNodeImpl = { [weak contentNode] in diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ae1164d0db..0c26f48fbc 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4423,7 +4423,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } if alwaysShow { - self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: "You can update the visibility of sensitive media in [Data and Storage > Show 18+ Content]().", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { + $0.withUpdatedShowSensitiveContent(true) + }).startStandalone() + + self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: self.presentationData.strings.SensitiveContent_SettingsInfo, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { [weak self] action in + if case .info = action, let self { + let controller = self.context.sharedContext.makeDataAndStorageController(context: self.context, sensitiveContent: true) + self.push(controller) + } + return false + }), in: .current) } reveal() }) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index c750ad853a..169d643d1d 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -350,7 +350,8 @@ private func extractAssociatedData( audioTranscriptionTrial: AudioTranscription.TrialState, chatThemes: [TelegramTheme], deviceContactsNumbers: Set, - isInline: Bool + isInline: Bool, + showSensitiveContent: Bool ) -> ChatMessageItemAssociatedData { var automaticDownloadPeerId: EnginePeer.Id? var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel @@ -405,7 +406,7 @@ private func extractAssociatedData( automaticDownloadPeerId = message.peerId } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent) } private extension ChatHistoryLocationInput { @@ -1557,6 +1558,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.allAdMessagesPromise.get() ) + let sharedData = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaDisplaySettings]) + let maxReadStoryId: Signal if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { maxReadStoryId = self.context.account.postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .peer(peerId))]) @@ -1632,9 +1635,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto recommendedChannels, audioTranscriptionTrial, chatThemes, - deviceContactsNumbers - ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers in + deviceContactsNumbers, + sharedData + ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, sharedData in let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises + let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings func applyHole() { Queue.mainQueue().async { @@ -1852,7 +1857,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto translateToLanguage = languageCode } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: mediaSettings.showSensitiveContent) var includeEmbeddedSavedChatInfo = false if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index e22e66a5b0..0dd3f9bda6 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1739,26 +1739,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }))) } } -// if message.id.peerId.isGroupOrChannel { -// //TODO:localize -// if message.isAgeRestricted() { -// actions.append(.action(ContextMenuActionItem(text: "Unmark as 18+", icon: { theme in -// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AgeUnmark"), color: theme.actionSheet.primaryTextColor) -// }, action: { c, _ in -// c?.dismiss(completion: { -// controllerInteraction.openMessageStats(messages[0].id) -// }) -// }))) -// } else { -// actions.append(.action(ContextMenuActionItem(text: "Mark as 18+", icon: { theme in -// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AgeMark"), color: theme.actionSheet.primaryTextColor) -// }, action: { c, _ in -// c?.dismiss(completion: { -// controllerInteraction.openMessageStats(messages[0].id) -// }) -// }))) -// } -// } if isReplyThreadHead { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ViewInChannel, icon: { theme in diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 6ecaf5fdfe..73ff9ae237 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2715,6 +2715,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return proxySettingsController(accountManager: sharedContext.accountManager, sharedContext: sharedContext, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) } + public func makeDataAndStorageController(context: AccountContext, sensitiveContent: Bool) -> ViewController { + return dataAndStorageController(context: context, focusOnItemTag: sensitiveContent ? DataAndStorageEntryTag.sensitiveContent : nil) + } + public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController { return installedStickerPacksController(context: context, mode: mode, forceTheme: forceTheme) } diff --git a/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift index 7532462dea..2573f6d518 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift @@ -4,33 +4,41 @@ import SwiftSignalKit public struct MediaDisplaySettings: Codable, Equatable { public let showNextMediaOnTap: Bool + public let showSensitiveContent: Bool public static var defaultSettings: MediaDisplaySettings { - return MediaDisplaySettings(showNextMediaOnTap: true) + return MediaDisplaySettings(showNextMediaOnTap: true, showSensitiveContent: false) } - public init(showNextMediaOnTap: Bool) { + public init(showNextMediaOnTap: Bool, showSensitiveContent: Bool) { self.showNextMediaOnTap = showNextMediaOnTap + self.showSensitiveContent = showSensitiveContent } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) self.showNextMediaOnTap = (try container.decode(Int32.self, forKey: "showNextMediaOnTap")) != 0 + self.showSensitiveContent = (try container.decode(Int32.self, forKey: "showSensitiveContent")) != 0 } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) try container.encode((self.showNextMediaOnTap ? 1 : 0) as Int32, forKey: "showNextMediaOnTap") + try container.encode((self.showSensitiveContent ? 1 : 0) as Int32, forKey: "showSensitiveContent") } public static func ==(lhs: MediaDisplaySettings, rhs: MediaDisplaySettings) -> Bool { - return lhs.showNextMediaOnTap == rhs.showNextMediaOnTap + return lhs.showNextMediaOnTap == rhs.showNextMediaOnTap && lhs.showSensitiveContent == rhs.showSensitiveContent } public func withUpdatedShowNextMediaOnTap(_ showNextMediaOnTap: Bool) -> MediaDisplaySettings { - return MediaDisplaySettings(showNextMediaOnTap: showNextMediaOnTap) + return MediaDisplaySettings(showNextMediaOnTap: showNextMediaOnTap, showSensitiveContent: self.showSensitiveContent) + } + + public func withUpdatedShowSensitiveContent(_ showSensitiveContent: Bool) -> MediaDisplaySettings { + return MediaDisplaySettings(showNextMediaOnTap: self.showNextMediaOnTap, showSensitiveContent: showSensitiveContent) } } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index bc5e329a88..b807153dcd 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -231,7 +231,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) if let phone = phone, let hash = hash { return .cancelAccountReset(phone: phone, hash: hash) } - } else if peerName == "msg" { + } else if peerName == "msg" || peerName == "share" { var url: String? var text: String? var to: String? @@ -241,7 +241,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) url = value } else if queryItem.name == "text" { text = value - } else if queryItem.name == "to" { + } else if queryItem.name == "to" && peerName != "share" { to = value } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 954c427f96..2c879e3fb9 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -2072,24 +2072,31 @@ public final class WebAppController: ViewController, AttachmentContainable { }) }))) - if let botCommands { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_PrivacyPolicy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Privacy"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + guard let self else { + return + } + + (self.parentController() as? AttachmentController)?.minimizeIfNeeded() + if let botCommands, botCommands.contains(where: { $0.text == "privacy" }) { + let _ = enqueueMessages(account: self.context.account, peerId: self.botId, messages: [.message(text: "/privacy", attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() + + if let botPeer, let navigationController = self.getNavigationController() { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(botPeer))) + } + } else { + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: self.presentationData.strings.WebApp_PrivacyPolicy_URL, forceExternal: false, presentationData: self.presentationData, navigationController: self.getNavigationController(), dismissInput: {}) + } + }))) + + if let botCommands, botCommands.contains(where: { $0.text == "privacy" }) { for command in botCommands { if command.text == "privacy" { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_PrivacyPolicy, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Privacy"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] c, _ in - c?.dismiss(completion: nil) - - guard let self else { - return - } - let _ = enqueueMessages(account: self.context.account, peerId: self.botId, messages: [.message(text: "/privacy", attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() - - if let botPeer, let navigationController = self.getNavigationController() { - (self.parentController() as? AttachmentController)?.minimizeIfNeeded() - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(botPeer))) - } - }))) + } } } diff --git a/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift b/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift index 85550aadf3..534d977642 100644 --- a/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift +++ b/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift @@ -16,8 +16,8 @@ import Markdown private let textFont = Font.regular(13.0) private let boldTextFont = Font.semibold(13.0) -private func formattedText(_ text: String, color: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { - return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: color), linkAttribute: { _ in return nil}), textAlignment: textAlignment) +private func formattedText(_ text: String, color: UIColor, linkColor: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { + return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { _ in return nil}), textAlignment: textAlignment) } private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { @@ -44,6 +44,7 @@ private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { private var validLayout: CGSize? private let morePressed: () -> Void + private let termsPressed: () -> Void override var dismissOnOutsideTap: Bool { return self.isUserInteractionEnabled @@ -55,13 +56,14 @@ private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { } } - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, title: String, text: String, showMore: Bool, requestWriteAccess: Bool, actions: [TextAlertAction], morePressed: @escaping () -> Void) { + init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, title: String, text: String, showMore: Bool, requestWriteAccess: Bool, actions: [TextAlertAction], morePressed: @escaping () -> Void, termsPressed: @escaping () -> Void) { self.strings = strings self.peer = peer self.title = title self.text = text self.showMore = showMore self.morePressed = morePressed + self.termsPressed = termsPressed self.titleNode = ImmediateTextNode() self.titleNode.displaysAsynchronously = false @@ -145,6 +147,8 @@ private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { super.didLoad() self.allowWriteLabelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.allowWriteTap(_:)))) + + self.textNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.termsTap(_:)))) } @objc private func allowWriteTap(_ gestureRecognizer: UITapGestureRecognizer) { @@ -153,18 +157,22 @@ private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { } } + @objc private func termsTap(_ gestureRecognizer: UITapGestureRecognizer) { + self.termsPressed() + } + @objc private func moreButtonPressed() { self.morePressed() } override func updateTheme(_ theme: AlertControllerTheme) { self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = formattedText(self.text, color: theme.primaryColor, linkColor: theme.accentColor, textAlignment: .center) self.moreButton.setAttributedTitle(NSAttributedString(string: self.strings.WebApp_LaunchMoreInfo, font: Font.regular(13.0), textColor: theme.accentColor), for: .normal) self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.accentColor) - self.allowWriteLabelNode.attributedText = formattedText(strings.WebApp_AddToAttachmentAllowMessages(self.peer.compactDisplayTitle).string, color: theme.primaryColor) + self.allowWriteLabelNode.attributedText = formattedText(strings.WebApp_AddToAttachmentAllowMessages(self.peer.compactDisplayTitle).string, color: theme.primaryColor, linkColor: theme.primaryColor) self.actionNodesSeparator.backgroundColor = theme.separatorColor for actionNode in self.actionNodes { @@ -313,7 +321,15 @@ private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { } } -public func webAppLaunchConfirmationController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, requestWriteAccess: Bool = false, completion: @escaping (Bool) -> Void, showMore: (() -> Void)?) -> AlertController { +public func webAppLaunchConfirmationController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, + peer: EnginePeer, + requestWriteAccess: Bool = false, + completion: @escaping (Bool) -> Void, + showMore: (() -> Void)?, + openTerms: @escaping () -> Void +) -> AlertController { let theme = defaultDarkColorPresentationTheme let presentationData: PresentationData if let updatedPresentationData { @@ -337,11 +353,14 @@ public func webAppLaunchConfirmationController(context: AccountContext, updatedP })] let title = peer.compactDisplayTitle - let text = presentationData.strings.WebApp_LaunchConfirmation + let text = presentationData.strings.WebApp_LaunchTermsConfirmation let contentNode = WebAppLaunchConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, title: title, text: text, showMore: showMore != nil, requestWriteAccess: requestWriteAccess, actions: actions, morePressed: { dismissImpl?(true) showMore?() + }, termsPressed: { + dismissImpl?(true) + openTerms() }) getContentNodeImpl = { [weak contentNode] in return contentNode