Various improvements

This commit is contained in:
Ilya Laktyushin 2024-03-26 14:23:18 +04:00
parent 26c03bd265
commit 2253fa1550
7 changed files with 334 additions and 204 deletions

View File

@ -11729,3 +11729,28 @@ Sorry for the inconvenience.";
"Monetization.Intro.Info.Text_URL" = "https://ton.org";
"Monetization.Intro.Understood" = "Understood";
"ReportAd.Title" = "Report Ad";
"ReportAd.Help" = "Learn more about [Telegram Ad Policies and Guidelines]().";
"ReportAd.Help_URL" = "https://promote.telegram.org/guidelines";
"ReportAd.Reported" = "We will review this ad to ensure it matches our [Ad Policies and Guidelines]().";
"ReportAd.Hidden" = "Ads are hidden now.";
"AdsInfo.Title" = "About These Ads";
"AdsInfo.Info" = "Telegram Ads are very different from ads on other platforms. Ads such as this one:";
"AdsInfo.Respect.Title" = "Respect Your Privacy";
"AdsInfo.Respect.Text" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
"AdsInfo.Split.Title" = "Help the Channel Creator";
"AdsInfo.Split.Text" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
"AdsInfo.Ads.Title" = "Can Be Removed";
"AdsInfo.Ads.Text" = "You can turn off ads by subscribing to [Telegram Premium](), and Level 30 channels can remove them for their subscribers.";
"AdsInfo.Launch.Title" = "Can I Launch an Ad?";
"AdsInfo.Launch.Text" = "Anyone can create ads to display in this channel with minimal budgets. Check out the Telegram Ad Platform for details. [Learn More >]()";
"AdsInfo.Launch.Text_URL" = "https://promote.telegram.org";
"AdsInfo.Understood" = "Understood";
"Chat.ContextMenu.AboutAd" = "About This Ad";
"Chat.ContextMenu.ReportAd" = "Report Ad";
"Chat.ContextMenu.RemoveAd" = "Remove Ad";

View File

@ -106,6 +106,10 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
}
super.setContentOffset(contentOffset, animated: animated)
}
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView {
@ -363,14 +367,21 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
let previousContentSize = self.scrollView.contentSize
self.scrollView.contentSize = contentSize
self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height) + contentSize.height, left: 0.0, bottom: 0.0, right: 0.0)
self.ignoreScrolling = false
let updateContentSize = {
self.scrollView.contentSize = contentSize
self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height) + contentSize.height, left: 0.0, bottom: 0.0, right: 0.0)
}
if previousContentSize.height.isZero {
updateContentSize()
}
self.ignoreScrolling = false
if let currentAvailableSize = self.currentAvailableSize, currentAvailableSize.height != availableSize.height {
self.scrollView.contentOffset = CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height))
} else if component.followContentSizeChanges, !previousContentSize.height.isZero, previousContentSize != contentSize {
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)), size: availableSize))
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)), size: availableSize), completion: { _ in
updateContentSize()
})
}
if self.currentHasInputHeight != previousHasInputHeight {
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)), size: self.scrollView.bounds.size))

View File

@ -79,6 +79,7 @@ extension TelegramBirthday {
public enum UpdateBirthdayError {
case generic
case flood
}
func _internal_updateBirthday(account: Account, birthday: TelegramBirthday?) -> Signal<Never, UpdateBirthdayError> {
@ -86,9 +87,13 @@ func _internal_updateBirthday(account: Account, birthday: TelegramBirthday?) ->
if let _ = birthday {
flags |= (1 << 0)
}
return account.network.request(Api.functions.account.updateBirthday(flags: flags, birthday: birthday?.apiBirthday))
|> mapError { _ -> UpdateBirthdayError in
return .generic
return account.network.request(Api.functions.account.updateBirthday(flags: flags, birthday: birthday?.apiBirthday), automaticFloodWait: false)
|> mapError { error -> UpdateBirthdayError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Never, UpdateBirthdayError> in
return account.postbox.transaction { transaction -> Void in

View File

@ -20,16 +20,16 @@ private final class ScrollContent: CombinedComponent {
typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment)
let context: AccountContext
let openMore: () -> Void
let openPremium: () -> Void
let dismiss: () -> Void
init(
context: AccountContext,
openMore: @escaping () -> Void,
openPremium: @escaping () -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
self.openMore = openMore
self.openPremium = openPremium
self.dismiss = dismiss
}
@ -80,7 +80,8 @@ private final class ScrollContent: CombinedComponent {
let state = context.state
let theme = environment.theme
// let strings = environment.strings
let strings = environment.strings
let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 }
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
@ -95,9 +96,7 @@ private final class ScrollContent: CombinedComponent {
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
//TODO:localize
let spacing: CGFloat = 16.0
var contentSize = CGSize(width: context.availableSize.width, height: 30.0)
@ -137,7 +136,7 @@ private final class ScrollContent: CombinedComponent {
let title = title.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "About These Ads", font: titleFont, textColor: textColor)),
text: .plain(NSAttributedString(string: strings.AdsInfo_Title, font: titleFont, textColor: textColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
@ -153,7 +152,7 @@ private final class ScrollContent: CombinedComponent {
let text = text.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "Telegram Ads are very different from ads on other platforms. Ads such as this one:", font: textFont, textColor: secondaryTextColor)),
text: .plain(NSAttributedString(string: strings.AdsInfo_Info, font: textFont, textColor: secondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
@ -173,9 +172,9 @@ private final class ScrollContent: CombinedComponent {
AnyComponentWithIdentity(
id: "respect",
component: AnyComponent(ParagraphComponent(
title: "Respect Your Privacy",
title: strings.AdsInfo_Respect_Title,
titleColor: textColor,
text: "Ads on Telegram do not use your personal information and are based on the channel in which you see them.",
text: strings.AdsInfo_Respect_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Ads/Privacy",
@ -187,9 +186,9 @@ private final class ScrollContent: CombinedComponent {
AnyComponentWithIdentity(
id: "split",
component: AnyComponent(ParagraphComponent(
title: "Help the Channel Creator",
title: strings.AdsInfo_Split_Title,
titleColor: textColor,
text: "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.",
text: strings.AdsInfo_Split_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Ads/Split",
@ -201,13 +200,16 @@ private final class ScrollContent: CombinedComponent {
AnyComponentWithIdentity(
id: "ads",
component: AnyComponent(ParagraphComponent(
title: "Can Be Removed",
title: strings.AdsInfo_Ads_Title,
titleColor: textColor,
text: "You can turn off ads by subscribing to [Telegram Premium](), and Level 30 channels can remove them for their subscribers.",
text: strings.AdsInfo_Ads_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/BoostPerk/NoAds",
iconColor: linkColor
iconColor: linkColor,
action: {
component.openPremium()
}
))
)
)
@ -223,7 +225,7 @@ private final class ScrollContent: CombinedComponent {
contentSize.height += list.size.height
contentSize.height += spacing - 9.0
let infoTitleAttributedString = NSMutableAttributedString(string: "Can I Launch an Ad?", font: titleFont, textColor: textColor)
let infoTitleAttributedString = NSMutableAttributedString(string: strings.AdsInfo_Launch_Title, font: titleFont, textColor: textColor)
let infoTitle = infoTitle.update(
component: MultilineTextComponent(
text: .plain(infoTitleAttributedString),
@ -239,7 +241,7 @@ private final class ScrollContent: CombinedComponent {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
}
let infoString = "Anyone can create ads to display in this channel with minimal budgets. Check out the Telegram Ad Platform for details. [Learn More >]()"
let infoString = strings.AdsInfo_Launch_Text
let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string))
@ -249,7 +251,17 @@ private final class ScrollContent: CombinedComponent {
text: .plain(infoAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
lineSpacing: 0.2,
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.AdsInfo_Launch_Text_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 3.5, height: context.availableSize.height),
transition: .immediate
@ -287,7 +299,7 @@ private final class ScrollContent: CombinedComponent {
let actionButton = actionButton.update(
component: SolidRoundedButtonComponent(
title: "Understood",
title: strings.AdsInfo_Understood,
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: theme.list.itemCheckColors.fillColor,
backgroundColors: [],
@ -327,14 +339,14 @@ private final class ContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let openMore: () -> Void
let openPremium: () -> Void
init(
context: AccountContext,
openMore: @escaping () -> Void
openPremium: @escaping () -> Void
) {
self.context = context
self.openMore = openMore
self.openPremium = openPremium
}
static func ==(lhs: ContainerComponent, rhs: ContainerComponent) -> Bool {
@ -367,7 +379,7 @@ private final class ContainerComponent: CombinedComponent {
component: ScrollComponent<EnvironmentType>(
content: AnyComponent(ScrollContent(
context: context.component.context,
openMore: context.component.openMore,
openPremium: context.component.openPremium,
dismiss: {
controller()?.dismiss()
}
@ -387,49 +399,6 @@ private final class ContainerComponent: CombinedComponent {
availableSize: context.availableSize,
transition: context.transition
)
// let sheet = sheet.update(
// component: SheetComponent<EnvironmentType>(
// content: AnyComponent<EnvironmentType>(ScrollContent(
// context: context.component.context,
// openMore: context.component.openMore,
// dismiss: {
// animateOut.invoke(Action { _ in
// if let controller = controller() {
// controller.dismiss(completion: nil)
// }
// })
// }
// )),
// backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
// followContentSizeChanges: true,
// externalState: sheetExternalState,
// animateOut: animateOut
// ),
// environment: {
// environment
// SheetComponentEnvironment(
// isDisplaying: environment.value.isVisible,
// isCentered: environment.metrics.widthClass == .regular,
// hasInputHeight: !environment.inputHeight.isZero,
// regularMetricsSize: CGSize(width: 430.0, height: 900.0),
// dismiss: { animated in
// if animated {
// animateOut.invoke(Action { _ in
// if let controller = controller() {
// controller.dismiss(completion: nil)
// }
// })
// } else {
// if let controller = controller() {
// controller.dismiss(completion: nil)
// }
// }
// }
// )
// },
// availableSize: context.availableSize,
// transition: context.transition
// )
context.add(scroll
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
@ -448,11 +417,14 @@ public final class AdsInfoScreen: ViewControllerComponentContainer {
) {
self.context = context
var openPremiumImpl: (() -> Void)?
super.init(
context: context,
component: ContainerComponent(
context: context,
openMore: {}
openPremium: {
openPremiumImpl?()
}
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
@ -460,6 +432,20 @@ public final class AdsInfoScreen: ViewControllerComponentContainer {
)
self.navigationPresentation = .modal
openPremiumImpl = { [weak self] in
guard let self else {
return
}
let navigationController = self.navigationController
self.dismiss()
Queue.mainQueue().after(0.3) {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
navigationController?.pushViewController(controller, animated: true)
}
}
}
required public init(coder aDecoder: NSCoder) {
@ -475,6 +461,7 @@ private final class ParagraphComponent: CombinedComponent {
let accentColor: UIColor
let iconName: String
let iconColor: UIColor
let action: () -> Void
public init(
title: String,
@ -483,7 +470,8 @@ private final class ParagraphComponent: CombinedComponent {
textColor: UIColor,
accentColor: UIColor,
iconName: String,
iconColor: UIColor
iconColor: UIColor,
action: @escaping () -> Void = {}
) {
self.title = title
self.titleColor = titleColor
@ -492,6 +480,7 @@ private final class ParagraphComponent: CombinedComponent {
self.accentColor = accentColor
self.iconName = iconName
self.iconColor = iconColor
self.action = action
}
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
@ -557,8 +546,8 @@ private final class ParagraphComponent: CombinedComponent {
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
linkAttribute: { _ in
return nil
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
@ -567,7 +556,17 @@ private final class ParagraphComponent: CombinedComponent {
text: .markdown(text: component.text, attributes: markdownAttributes),
horizontalAlignment: .natural,
maximumNumberOfLines: 0,
lineSpacing: 0.2
lineSpacing: 0.2,
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
component.action()
}
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
transition: .immediate

View File

@ -17,6 +17,12 @@ import ItemListUI
import UndoUI
import AccountContext
private enum ReportResult {
case reported
case hidden
case premiumRequired
}
private final class SheetPageContent: CombinedComponent {
struct Item: Equatable {
let title: String
@ -83,7 +89,7 @@ private final class SheetPageContent: CombinedComponent {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme
// let strings = environment.strings
let strings = presentationData.strings
let sideInset: CGFloat = 16.0
@ -108,12 +114,12 @@ private final class SheetPageContent: CombinedComponent {
let backContents: AnyComponent<Empty>
if component.title == nil {
backContents = AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: theme.list.itemAccentColor))
backContents = AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.list.itemAccentColor))
} else {
backContents = AnyComponent(
HStack([
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(Image(image: backArrowImage, contentMode: .center))),
AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "Back", font: Font.regular(17.0), color: theme.list.itemAccentColor)))
AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: strings.Common_Back, font: Font.regular(17.0), color: theme.list.itemAccentColor)))
], spacing: 6.0)
)
}
@ -132,7 +138,7 @@ private final class SheetPageContent: CombinedComponent {
)
let title = title.update(
component: Text(text: "Report Ad", font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
component: Text(text: strings.ReportAd_Title, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: .immediate
)
@ -193,17 +199,18 @@ private final class SheetPageContent: CombinedComponent {
)),
footer: AnyComponent(MultilineTextComponent(
text: .markdown(
text: "Learn more about [Telegram Ad Policies and Guidelines]().",
text: strings.ReportAd_Help,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.itemAccentColor),
linkAttribute: { _ in
return nil
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
),
maximumNumberOfLines: 0,
highlightColor: theme.list.itemAccentColor.withAlphaComponent(0.2),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
@ -212,7 +219,7 @@ private final class SheetPageContent: CombinedComponent {
}
},
tapAction: { _, _ in
// component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Monetization_Intro_Info_Text_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.ReportAd_Help_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
)),
items: items
@ -242,7 +249,7 @@ private final class SheetContent: CombinedComponent {
let options: [ReportAdMessageResult.Option]
let pts: Int
let openMore: () -> Void
let complete: () -> Void
let complete: (ReportResult) -> Void
let dismiss: () -> Void
let update: (Transition) -> Void
@ -254,7 +261,7 @@ private final class SheetContent: CombinedComponent {
options: [ReportAdMessageResult.Option],
pts: Int,
openMore: @escaping () -> Void,
complete: @escaping () -> Void,
complete: @escaping (ReportResult) -> Void,
dismiss: @escaping () -> Void,
update: @escaping (Transition) -> Void
) {
@ -330,9 +337,13 @@ private final class SheetContent: CombinedComponent {
state?.pushedOptions = (item.title, title, options)
state?.updated(transition: .spring(duration: 0.45))
case .adsHidden:
complete()
complete(.hidden)
case .reported:
complete()
complete(.reported)
}
}, error: { error in
if case .premiumRequired = error {
complete(.premiumRequired)
}
})
)
@ -377,7 +388,13 @@ private final class SheetContent: CombinedComponent {
var contentSize = CGSize(width: context.availableSize.width, height: 0.0)
let navigation = navigation.update(
component: NavigationStackComponent(items: items),
component: NavigationStackComponent(
items: items,
requestPop: { [weak state] in
state?.pushedOptions = nil
update(.spring(duration: 0.45))
}
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: context.transition
)
@ -402,7 +419,7 @@ private final class SheetContainerComponent: CombinedComponent {
let title: String
let options: [ReportAdMessageResult.Option]
let openMore: () -> Void
let complete: () -> Void
let complete: (ReportResult) -> Void
init(
context: AccountContext,
@ -411,7 +428,7 @@ private final class SheetContainerComponent: CombinedComponent {
title: String,
options: [ReportAdMessageResult.Option],
openMore: @escaping () -> Void,
complete: @escaping () -> Void
complete: @escaping (ReportResult) -> Void
) {
self.context = context
self.peerId = peerId
@ -552,7 +569,7 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
) {
self.context = context
var completeImpl: (() -> Void)?
var completeImpl: ((ReportResult) -> Void)?
super.init(
context: context,
component: SheetContainerComponent(
@ -562,8 +579,8 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
title: title,
options: options,
openMore: {},
complete: {
completeImpl?()
complete: { hidden in
completeImpl?(hidden)
}
),
navigationBarAppearance: .none,
@ -573,7 +590,7 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
self.navigationPresentation = .flatModal
completeImpl = { [weak self] in
completeImpl = { [weak self] result in
guard let self else {
return
}
@ -581,11 +598,32 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
self.dismissAnimated()
Queue.mainQueue().after(0.4, {
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
(navigationController?.viewControllers.last as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "We will review this ad to ensure it matches our [Ad Policies and Guidelines]().", cancel: nil, destructive: false), elevatedLayout: false, action: { _ in
return true
}), in: .current)
switch result {
case .reported, .hidden:
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text: String
if case .reported = result {
text = presentationData.strings.ReportAd_Reported
} else {
text = presentationData.strings.ReportAd_Hidden
}
(navigationController?.viewControllers.last as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: text, cancel: nil, destructive: false), elevatedLayout: false, action: { action in
if case .info = action, case .reported = result {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.ReportAd_Help_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
return true
}), in: .current)
case .premiumRequired:
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
navigationController?.pushViewController(controller, animated: true)
}
})
}
}
@ -609,84 +647,89 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
//private final class NavigationContainer: UIView, UIGestureRecognizerDelegate {
// var requestUpdate: ((Transition) -> Void)?
// var requestPop: (() -> Void)?
// var transitionFraction: CGFloat = 0.0
//
// private var panRecognizer: InteractiveTransitionGestureRecognizer?
//
// var isNavigationEnabled: Bool = false {
// didSet {
// self.panRecognizer?.isEnabled = self.isNavigationEnabled
// }
// }
//
// override init() {
// super.init()
//
// self.clipsToBounds = true
//
// let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
// guard let strongSelf = self else {
// return []
// }
// let _ = strongSelf
// return [.right]
// })
// panRecognizer.delegate = self
// self.view.addGestureRecognizer(panRecognizer)
// self.panRecognizer = panRecognizer
// }
//
// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// return false
// }
//
// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
// return false
// }
// if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
// return true
// }
// return false
// }
//
// @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
// switch recognizer.state {
// case .began:
// self.transitionFraction = 0.0
// case .changed:
// let distanceFactor: CGFloat = recognizer.translation(in: self.view).x / self.bounds.width
// let transitionFraction = max(0.0, min(1.0, distanceFactor))
// if self.transitionFraction != transitionFraction {
// self.transitionFraction = transitionFraction
// self.requestUpdate?(.immediate)
// }
// case .ended, .cancelled:
// let distanceFactor: CGFloat = recognizer.translation(in: self.view).x / self.bounds.width
// let transitionFraction = max(0.0, min(1.0, distanceFactor))
// if transitionFraction > 0.2 {
// self.transitionFraction = 0.0
// self.requestPop?()
// } else {
// self.transitionFraction = 0.0
// self.requestUpdate?(.spring(duration: 0.45))
// }
// default:
// break
// }
// }
//}
private final class NavigationContainer: UIView, UIGestureRecognizerDelegate {
var requestUpdate: ((Transition) -> Void)?
var requestPop: (() -> Void)?
var transitionFraction: CGFloat = 0.0
private var panRecognizer: InteractiveTransitionGestureRecognizer?
var isNavigationEnabled: Bool = false {
didSet {
self.panRecognizer?.isEnabled = self.isNavigationEnabled
}
}
init() {
super.init(frame: .zero)
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let strongSelf = self else {
return []
}
let _ = strongSelf
return [.right]
})
panRecognizer.delegate = self
self.addGestureRecognizer(panRecognizer)
self.panRecognizer = panRecognizer
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
return false
}
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
return true
}
return false
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
self.transitionFraction = 0.0
case .changed:
let distanceFactor: CGFloat = recognizer.translation(in: self).x / self.bounds.width
let transitionFraction = max(0.0, min(1.0, distanceFactor))
if self.transitionFraction != transitionFraction {
self.transitionFraction = transitionFraction
self.requestUpdate?(.immediate)
}
case .ended, .cancelled:
let distanceFactor: CGFloat = recognizer.translation(in: self).x / self.bounds.width
let transitionFraction = max(0.0, min(1.0, distanceFactor))
if transitionFraction > 0.2 {
self.transitionFraction = 0.0
self.requestPop?()
} else {
self.transitionFraction = 0.0
self.requestUpdate?(.spring(duration: 0.45))
}
default:
break
}
}
}
final class NavigationStackComponent: Component {
public let items: [AnyComponentWithIdentity<Empty>]
public let requestPop: () -> Void
public init(
items: [AnyComponentWithIdentity<Empty>]
items: [AnyComponentWithIdentity<Empty>],
requestPop: @escaping () -> Void
) {
self.items = items
self.requestPop = requestPop
}
public static func ==(lhs: NavigationStackComponent, rhs: NavigationStackComponent) -> Bool {
@ -716,11 +759,29 @@ final class NavigationStackComponent: Component {
public final class View: UIView {
private var itemViews: [AnyHashable: ItemView] = [:]
private let navigationContainer = NavigationContainer()
private var component: NavigationStackComponent?
private var state: EmptyComponentState?
public override init(frame: CGRect) {
super.init(frame: CGRect())
self.addSubview(self.navigationContainer)
self.navigationContainer.requestUpdate = { [weak self] transition in
guard let self else {
return
}
self.state?.updated(transition: transition)
}
self.navigationContainer.requestPop = { [weak self] in
guard let self else {
return
}
self.component?.requestPop()
}
}
required public init?(coder: NSCoder) {
@ -729,9 +790,11 @@ final class NavigationStackComponent: Component {
func update(component: NavigationStackComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.state = state
var contentHeight: CGFloat = 0.0
let navigationTransitionFraction = self.navigationContainer.transitionFraction
self.navigationContainer.isNavigationEnabled = component.items.count > 1
var validItemIds: [AnyHashable] = []
struct ReadyItem {
var index: Int
@ -762,7 +825,6 @@ final class NavigationStackComponent: Component {
} else {
itemTransition = itemTransition.withAnimation(.none)
itemView = ItemView()
itemView.clipsToBounds = true
self.itemViews[itemId] = itemView
itemView.contents.parentState = state
}
@ -781,15 +843,32 @@ final class NavigationStackComponent: Component {
itemTransition: itemTransition,
itemSize: itemSize
))
if i == component.items.count - 1 {
contentHeight = itemSize.height
}
}
for readyItem in readyItems.sorted(by: { $0.index < $1.index }) {
let isLast = readyItem.index == readyItems.count - 1
let itemFrame = CGRect(origin: CGPoint(x: isLast ? 0.0 : -readyItem.itemSize.width / 3.0, y: 0.0), size: readyItem.itemSize)
let sortedItems = readyItems.sorted(by: { $0.index < $1.index })
for readyItem in sortedItems {
let transitionFraction: CGFloat
let alphaTransitionFraction: CGFloat
if readyItem.index == readyItems.count - 1 {
transitionFraction = navigationTransitionFraction
alphaTransitionFraction = 1.0
} else if readyItem.index == readyItems.count - 2 {
transitionFraction = navigationTransitionFraction - 1.0
alphaTransitionFraction = navigationTransitionFraction
} else {
transitionFraction = 0.0
alphaTransitionFraction = 0.0
}
let transitionOffset: CGFloat
if readyItem.index == readyItems.count - 1 {
transitionOffset = readyItem.itemSize.width * transitionFraction
} else {
transitionOffset = readyItem.itemSize.width / 3.0 * transitionFraction
}
let itemFrame = CGRect(origin: CGPoint(x: transitionOffset, y: 0.0), size: readyItem.itemSize)
let itemBounds = CGRect(origin: .zero, size: itemFrame.size)
if let itemComponentView = readyItem.itemView.contents.view {
var isAdded = false
@ -797,12 +876,12 @@ final class NavigationStackComponent: Component {
isAdded = true
readyItem.itemView.insertSubview(itemComponentView, at: 0)
self.addSubview(readyItem.itemView)
self.navigationContainer.addSubview(readyItem.itemView)
}
readyItem.itemTransition.setFrame(view: readyItem.itemView, frame: itemFrame)
readyItem.itemTransition.setFrame(view: itemComponentView, frame: itemBounds)
readyItem.itemTransition.setFrame(view: readyItem.itemView.dimView, frame: itemBounds)
readyItem.itemTransition.setAlpha(view: readyItem.itemView.dimView, alpha: isLast ? 0.0 : 1.0)
readyItem.itemTransition.setAlpha(view: readyItem.itemView.dimView, alpha: 1.0 - alphaTransitionFraction)
if readyItem.index > 0 && isAdded {
transition.animatePosition(view: itemComponentView, from: CGPoint(x: itemFrame.width, y: 0.0), to: .zero, additive: true, completion: nil)
@ -810,6 +889,15 @@ final class NavigationStackComponent: Component {
}
}
let lastHeight = sortedItems.last?.itemSize.height ?? 0.0
let previousHeight: CGFloat
if sortedItems.count > 1 {
previousHeight = sortedItems[sortedItems.count - 2].itemSize.height
} else {
previousHeight = lastHeight
}
let contentHeight = lastHeight * (1.0 - navigationTransitionFraction) + previousHeight * navigationTransitionFraction
var removedItemIds: [AnyHashable] = []
for (id, _) in self.itemViews {
if !validItemIds.contains(id) {
@ -833,7 +921,10 @@ final class NavigationStackComponent: Component {
}
}
return CGSize(width: availableSize.width, height: contentHeight)
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
self.navigationContainer.frame = CGRect(origin: .zero, size: contentSize)
return contentSize
}
}

View File

@ -30,7 +30,6 @@ import DebugSettingsUI
import ChatPresentationInterfaceState
import Pasteboard
import SettingsUI
import PremiumUI
import TextNodeWithEntities
import ChatControllerInteraction
import ChatMessageItemCommon
@ -477,21 +476,18 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
var actions: [ContextMenuItem] = []
if "".isEmpty {
// if adAttribute.canReport {
//TODO:localize
actions.append(.action(ContextMenuActionItem(text: "About This Ad", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
if adAttribute.canReport {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AboutAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { _, f in
f(.dismissWithoutContent)
controllerInteraction.navigationController()?.pushViewController(AdsInfoScreen(context: context))
})))
actions.append(.action(ContextMenuActionItem(text: "Report Ad", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { _, f in
f(.dismissWithoutContent)
f(.default)
let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { result in
@ -511,13 +507,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.append(.separator)
actions.append(.action(ContextMenuActionItem(text: "Remove Ad", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { c, _ in
c.dismiss(completion: {
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .noAds, action: {
let controller = PremiumIntroScreen(context: context, source: .ads)
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
@ -585,8 +581,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}, iconSource: nil, action: { c, _ in
c.dismiss(completion: {
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .noAds, action: {
let controller = PremiumIntroScreen(context: context, source: .ads)
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
@ -1083,15 +1079,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}, action: { _, f in
let context = context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .fasterDownload, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
controllerInteraction.navigationController()?.pushViewController(controller)
f(.dismissWithoutContent)
})))
actions.append(.separator)
@ -1626,7 +1621,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .info = action {
let controller = PremiumIntroScreen(context: context, source: .savedGifs)
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: false, dismissed: nil)
controllerInteraction.navigationController()?.pushViewController(controller)
return true
}

View File

@ -279,6 +279,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false
}
self.originalRemainingSeconds = 5
if text.contains("](") {
isUserInteractionEnabled = true
}
case let .linkCopied(text):
self.avatarNode = nil
self.iconNode = nil