mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
c92ce1c1dc
commit
83c7fdd228
@ -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";
|
||||
|
@ -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<PresentationData, NoError>)?, peerId: EnginePeer.Id, boosts: Bool, boostStatus: ChannelBoostStatus?) -> ViewController
|
||||
|
@ -62,6 +62,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
public let deviceContactsNumbers: Set<String>
|
||||
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<String> = 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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 = ""
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -232,6 +232,12 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13
|
||||
}, initialValues: [:], queue: queue)
|
||||
}
|
||||
|
||||
public func combineLatest<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>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>, _ s13: Signal<T13, E>, _ s14: Signal<T14, E>, _ s15: Signal<T15, E>, _ s16: Signal<T16, E>, _ s17: Signal<T17, E>, _ s18: Signal<T18, E>, _ s19: Signal<T19, E>, _ s20: Signal<T20, E>, _ s21: Signal<T21, E>, _ s22: Signal<T22, E>, _ s23: Signal<T23, E>, _ s24: Signal<T24, E>, _ s25: Signal<T25, E>) -> 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<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
|
||||
if signals.count == 0 {
|
||||
return single([T](), E.self)
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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<Api.Updates>) {
|
||||
static func sendPaidReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, count: Int32, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Never, RequestUpdateMessageReactionError> = network.request(Api.functions.messages.sendPaidReaction(peer: inputPeer, msgId: messageId.id, count: count, randomId: Int64(bitPattern: randomId)))
|
||||
let signal: Signal<Never, RequestUpdateMessageReactionError> = network.request(Api.functions.messages.sendPaidReaction(flags: 0, peer: inputPeer, msgId: messageId.id, count: count, randomId: Int64(bitPattern: randomId)))
|
||||
|> mapError { _ -> RequestUpdateMessageReactionError in
|
||||
return .generic
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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<FetchControls?>(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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -524,7 +524,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
self.navigationPresentation = .standaloneModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -350,7 +350,8 @@ private func extractAssociatedData(
|
||||
audioTranscriptionTrial: AudioTranscription.TrialState,
|
||||
chatThemes: [TelegramTheme],
|
||||
deviceContactsNumbers: Set<String>,
|
||||
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<Int32?, NoError>
|
||||
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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
})))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<PresentationData, NoError>)?, peer: EnginePeer, requestWriteAccess: Bool = false, completion: @escaping (Bool) -> Void, showMore: (() -> Void)?) -> AlertController {
|
||||
public func webAppLaunchConfirmationController(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user