Move limits page to horizontal scroll

This commit is contained in:
Ilya Laktyushin 2022-10-25 19:56:38 +03:00
parent 0ffcbf0b06
commit f8c5bf2a6b
7 changed files with 878 additions and 291 deletions

View File

@ -17,7 +17,7 @@ import SolidRoundedButtonComponent
import Markdown import Markdown
import TelegramUIPreferences import TelegramUIPreferences
private final class GradientBackgroundComponent: Component { final class GradientBackgroundComponent: Component {
public let colors: [UIColor] public let colors: [UIColor]
public init( public init(
@ -153,7 +153,7 @@ final class DemoPageEnvironment: Equatable {
} }
} }
private final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent { final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent {
typealias EnvironmentType = ChildEnvironment typealias EnvironmentType = ChildEnvironment
private let content: AnyComponent<ChildEnvironment> private let content: AnyComponent<ChildEnvironment>
@ -263,7 +263,7 @@ private final class PageComponent<ChildEnvironment: Equatable>: CombinedComponen
} }
} }
private final class DemoPagerComponent: Component { final class DemoPagerComponent: Component {
public final class Item: Equatable { public final class Item: Equatable {
public let content: AnyComponentWithIdentity<DemoPageEnvironment> public let content: AnyComponentWithIdentity<DemoPageEnvironment>
@ -282,40 +282,29 @@ private final class DemoPagerComponent: Component {
let items: [Item] let items: [Item]
let index: Int let index: Int
let activeColor: UIColor let updated: (CGFloat, Int) -> Void
let inactiveColor: UIColor
public init( public init(
items: [Item], items: [Item],
index: Int = 0, index: Int = 0,
activeColor: UIColor, updated: @escaping (CGFloat, Int) -> Void
inactiveColor: UIColor
) { ) {
self.items = items self.items = items
self.index = index self.index = index
self.activeColor = activeColor self.updated = updated
self.inactiveColor = inactiveColor
} }
public static func ==(lhs: DemoPagerComponent, rhs: DemoPagerComponent) -> Bool { public static func ==(lhs: DemoPagerComponent, rhs: DemoPagerComponent) -> Bool {
if lhs.items != rhs.items { if lhs.items != rhs.items {
return false return false
} }
if !lhs.activeColor.isEqual(rhs.activeColor) {
return false
}
if !lhs.inactiveColor.isEqual(rhs.inactiveColor) {
return false
}
return true return true
} }
fileprivate final class View: UIView, UIScrollViewDelegate { final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView private let scrollView: UIScrollView
private var itemViews: [AnyHashable: ComponentHostView<DemoPageEnvironment>] = [:] private var itemViews: [AnyHashable: ComponentHostView<DemoPageEnvironment>] = [:]
private let pageIndicatorView: ComponentHostView<Empty>
private var component: DemoPagerComponent? private var component: DemoPagerComponent?
override init(frame: CGRect) { override init(frame: CGRect) {
@ -327,15 +316,11 @@ private final class DemoPagerComponent: Component {
self.scrollView.bounces = false self.scrollView.bounces = false
self.scrollView.layer.cornerRadius = 10.0 self.scrollView.layer.cornerRadius = 10.0
self.pageIndicatorView = ComponentHostView<Empty>()
self.pageIndicatorView.isUserInteractionEnabled = false
super.init(frame: frame) super.init(frame: frame)
self.scrollView.delegate = self self.scrollView.delegate = self
self.addSubview(self.scrollView) self.addSubview(self.scrollView)
self.addSubview(self.pageIndicatorView)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -350,6 +335,7 @@ private final class DemoPagerComponent: Component {
self.ignoreContentOffsetChange = true self.ignoreContentOffsetChange = true
let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate) let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate)
component.updated(self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width), component.items.count)
self.ignoreContentOffsetChange = false self.ignoreContentOffsetChange = false
} }
@ -369,6 +355,7 @@ private final class DemoPagerComponent: Component {
if firstTime { if firstTime {
self.scrollView.contentOffset = CGPoint(x: CGFloat(component.index) * availableSize.width, y: 0.0) self.scrollView.contentOffset = CGPoint(x: CGFloat(component.index) * availableSize.width, y: 0.0)
component.updated(self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width), component.items.count)
} }
let viewportCenter = self.scrollView.contentOffset.x + availableSize.width * 0.5 let viewportCenter = self.scrollView.contentOffset.x + availableSize.width * 0.5
@ -398,7 +385,6 @@ private final class DemoPagerComponent: Component {
itemView = ComponentHostView<DemoPageEnvironment>() itemView = ComponentHostView<DemoPageEnvironment>()
self.itemViews[item.content.id] = itemView self.itemViews[item.content.id] = itemView
if item.content.id == (PremiumDemoScreen.Subject.fasterDownload as AnyHashable) { if item.content.id == (PremiumDemoScreen.Subject.fasterDownload as AnyHashable) {
self.scrollView.insertSubview(itemView, at: 0) self.scrollView.insertSubview(itemView, at: 0)
} else { } else {
@ -430,33 +416,15 @@ private final class DemoPagerComponent: Component {
self.component = component self.component = component
if component.items.count > 1 {
let pageIndicatorComponent = PageIndicatorComponent(
pageCount: component.items.count,
position: self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - availableSize.width),
inactiveColor: component.inactiveColor,
activeColor: component.activeColor
)
let indicatorSize = self.pageIndicatorView.update(
transition: .immediate,
component: AnyComponent(
pageIndicatorComponent
),
environment: {},
containerSize: availableSize
)
self.pageIndicatorView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - indicatorSize.width) / 2.0), y: availableSize.height - indicatorSize.height - 11.0), size: indicatorSize)
}
return availableSize return availableSize
} }
} }
public func makeView() -> View { func makeView() -> View {
return View(frame: CGRect()) return View(frame: CGRect())
} }
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition) return view.update(component: self, availableSize: availableSize, transition: transition)
} }
} }
@ -949,8 +917,7 @@ private final class DemoSheetContent: CombinedComponent {
component: DemoPagerComponent( component: DemoPagerComponent(
items: items, items: items,
index: index, index: index,
activeColor: UIColor(rgb: 0x7169ff), updated: { _, _ in }
inactiveColor: theme.list.disclosureArrowColor
), ),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.width + 154.0), availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.width + 154.0),
transition: context.transition transition: context.transition
@ -1177,6 +1144,7 @@ private final class DemoSheetComponent: CombinedComponent {
public class PremiumDemoScreen: ViewControllerComponentContainer { public class PremiumDemoScreen: ViewControllerComponentContainer {
public enum Subject { public enum Subject {
case doubleLimits
case moreUpload case moreUpload
case fasterDownload case fasterDownload
case voiceToText case voiceToText

View File

@ -361,21 +361,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
var demoSubject: PremiumDemoScreen.Subject var demoSubject: PremiumDemoScreen.Subject
switch perk { switch perk {
case .doubleLimits: case .doubleLimits:
var dismissImpl: (() -> Void)? demoSubject = .doubleLimits
let controller = PremimLimitsListScreen(context: accountContext, buttonText: strings.Premium_Gift_GiftSubscription(state?.price ?? "").string, isPremium: false)
controller.action = {
dismissImpl?()
buy()
}
controller.disposed = {
// updateIsFocused(false)
}
present(controller)
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true, completion: nil)
}
// updateIsFocused(true)
return
case .moreUpload: case .moreUpload:
demoSubject = .moreUpload demoSubject = .moreUpload
case .fasterDownload: case .fasterDownload:
@ -402,20 +388,34 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
demoSubject = .emojiStatus demoSubject = .emojiStatus
} }
let controller = PremiumDemoScreen( var dismissImpl: (() -> Void)?
context: accountContext, let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .gift(state?.price), order: state?.configuration.perks, buttonText: strings.Premium_Gift_GiftSubscription(state?.price ?? "").string, isPremium: false)
subject: demoSubject, controller.action = {
source: .gift(state?.price), dismissImpl?()
order: state?.configuration.perks, buy()
action: { }
buy()
}
)
controller.disposed = { controller.disposed = {
// updateIsFocused(false) // updateIsFocused(false)
} }
present(controller) present(controller)
// updateIsFocused(true) dismissImpl = { [weak controller] in
controller?.dismiss(animated: true, completion: nil)
}
// let controller = PremiumDemoScreen(
// context: accountContext,
// subject: demoSubject,
// source: .gift(state?.price),
// order: state?.configuration.perks,
// action: {
// buy()
// }
// )
// controller.disposed = {
//// updateIsFocused(false)
// }
// present(controller)
//// updateIsFocused(true)
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
} }

View File

@ -1556,25 +1556,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var demoSubject: PremiumDemoScreen.Subject var demoSubject: PremiumDemoScreen.Subject
switch perk { switch perk {
case .doubleLimits: case .doubleLimits:
let isPremium = state?.isPremium == true demoSubject = .doubleLimits
var dismissImpl: (() -> Void)?
let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "").string : strings.Premium_SubscribeFor(state?.price ?? "").string), isPremium: isPremium)
controller.action = { [weak state] in
dismissImpl?()
if state?.isPremium == false {
buy()
}
}
controller.disposed = {
updateIsFocused(false)
}
present(controller)
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true, completion: nil)
}
updateIsFocused(true)
return
case .moreUpload: case .moreUpload:
demoSubject = .moreUpload demoSubject = .moreUpload
case .fasterDownload: case .fasterDownload:
@ -1601,23 +1583,42 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
demoSubject = .emojiStatus demoSubject = .emojiStatus
} }
let controller = PremiumDemoScreen( let isPremium = state?.isPremium == true
context: accountContext,
subject: demoSubject, var dismissImpl: (() -> Void)?
source: .intro(state?.price), let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.perks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "").string : strings.Premium_SubscribeFor(state?.price ?? "").string), isPremium: isPremium)
order: state?.configuration.perks, controller.action = { [weak state] in
action: { dismissImpl?()
if state?.isPremium == false { if state?.isPremium == false {
buy() buy()
}
} }
) }
controller.disposed = { controller.disposed = {
updateIsFocused(false) updateIsFocused(false)
} }
present(controller) present(controller)
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true, completion: nil)
}
updateIsFocused(true) updateIsFocused(true)
// let controller = PremiumDemoScreen(
// context: accountContext,
// subject: demoSubject,
// source: .intro(state?.price),
// order: state?.configuration.perks,
// action: {
// if state?.isPremium == false {
// buy()
// }
// }
// )
// controller.disposed = {
// updateIsFocused(false)
// }
// present(controller)
// updateIsFocused(true)
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
} }
)) ))

File diff suppressed because it is too large Load Diff

View File

@ -20,23 +20,26 @@ final class ScrollChildEnvironment: Equatable {
} }
final class ScrollComponent<ChildEnvironment: Equatable>: Component { final class ScrollComponent<ChildEnvironment: Equatable>: Component {
public typealias EnvironmentType = ChildEnvironment typealias EnvironmentType = ChildEnvironment
public let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)> let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>
public let contentInsets: UIEdgeInsets let contentInsets: UIEdgeInsets
public let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void
public let contentOffsetWillCommit: (UnsafeMutablePointer<CGPoint>) -> Void let contentOffsetWillCommit: (UnsafeMutablePointer<CGPoint>) -> Void
let resetScroll: ActionSlot<Void>
public init( public init(
content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>, content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>,
contentInsets: UIEdgeInsets, contentInsets: UIEdgeInsets,
contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void, contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void,
contentOffsetWillCommit: @escaping (UnsafeMutablePointer<CGPoint>) -> Void contentOffsetWillCommit: @escaping (UnsafeMutablePointer<CGPoint>) -> Void,
resetScroll: ActionSlot<Void> = ActionSlot()
) { ) {
self.content = content self.content = content
self.contentInsets = contentInsets self.contentInsets = contentInsets
self.contentOffsetUpdated = contentOffsetUpdated self.contentOffsetUpdated = contentOffsetUpdated
self.contentOffsetWillCommit = contentOffsetWillCommit self.contentOffsetWillCommit = contentOffsetWillCommit
self.resetScroll = resetScroll
} }
public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool { public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool {
@ -46,7 +49,6 @@ final class ScrollComponent<ChildEnvironment: Equatable>: Component {
if lhs.contentInsets != rhs.contentInsets { if lhs.contentInsets != rhs.contentInsets {
return false return false
} }
return true return true
} }
@ -107,6 +109,10 @@ final class ScrollComponent<ChildEnvironment: Equatable>: Component {
) )
transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil)
component.resetScroll.connect { [weak self] _ in
self?.setContentOffset(.zero, animated: false)
}
if self.contentSize != contentSize { if self.contentSize != contentSize {
self.ignoreDidScroll = true self.ignoreDidScroll = true
self.contentSize = contentSize self.contentSize = contentSize

View File

@ -8,7 +8,8 @@ private let whitelistedHosts: Set<String> = Set([
"t.me", "t.me",
"telegram.me", "telegram.me",
"telegra.ph", "telegra.ph",
"telesco.pe" "telesco.pe",
"fragment.com"
]) ])
private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue) private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue)

View File

@ -4,7 +4,8 @@ private let whitelistedHosts: Set<String> = Set([
"t.me", "t.me",
"telegram.me", "telegram.me",
"telegra.ph", "telegra.ph",
"telesco.pe" "telesco.pe",
"fragment.com"
]) ])
public func isConcealedUrlWhitelisted(_ url: URL) -> Bool { public func isConcealedUrlWhitelisted(_ url: URL) -> Bool {