diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 2085326af0..7d35ec11c5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -227,6 +227,14 @@ private final class BadgeComponent: Component { required init(coder: NSCoder) { preconditionFailure() } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.badgeView.frame.contains(point) { + return self + } else { + return nil + } + } func update(component: BadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { if self.component == nil { @@ -806,7 +814,7 @@ private final class SliderBackgroundComponent: Component { topBackgroundTextView.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: animateTopTextAdditionalX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.3, damping: 100.0, additive: true) } - topForegroundTextView.isHidden = component.topCutoff == nil + topForegroundTextView.isHidden = component.topCutoff == nil || topLineFrame.maxX + topTextSize.width + 20.0 > availableSize.width topBackgroundTextView.isHidden = topForegroundTextView.isHidden self.topBackgroundLine.isHidden = topX < 10.0 self.topForegroundLine.isHidden = self.topBackgroundLine.isHidden @@ -915,6 +923,83 @@ private final class ChatSendStarsScreenComponent: Component { } } + private struct Amount: Equatable { + private let sliderSteps: [Int] + private let maxRealValue: Int + let maxSliderValue: Int + private let isLogarithmic: Bool + + private(set) var realValue: Int + private(set) var sliderValue: Int + + private static func makeSliderSteps(maxRealValue: Int, isLogarithmic: Bool) -> [Int] { + if isLogarithmic { + var sliderSteps: [Int] = [ 1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ] + sliderSteps.removeAll(where: { $0 >= maxRealValue }) + sliderSteps.append(maxRealValue) + return sliderSteps + } else { + return [1, maxRealValue] + } + } + + private static func remapValueToSlider(realValue: Int, maxSliderValue: Int, steps: [Int]) -> Int { + guard realValue >= steps.first!, realValue <= steps.last! else { return 0 } + + for i in 0 ..< steps.count - 1 { + if realValue >= steps[i] && realValue <= steps[i + 1] { + let range = steps[i + 1] - steps[i] + let relativeValue = realValue - steps[i] + let stepFraction = Float(relativeValue) / Float(range) + return Int(Float(i) * Float(maxSliderValue) / Float(steps.count - 1)) + Int(stepFraction * Float(maxSliderValue) / Float(steps.count - 1)) + } + } + return maxSliderValue // Return max slider position if value equals the last step + } + + private static func remapSliderToValue(sliderValue: Int, maxSliderValue: Int, steps: [Int]) -> Int { + guard sliderValue >= 0, sliderValue <= maxSliderValue else { return steps.first! } + + let stepIndex = Int(Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1)) + let fraction = Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1) - Float(stepIndex) + + if stepIndex >= steps.count - 1 { + return steps.last! + } else { + let range = steps[stepIndex + 1] - steps[stepIndex] + return steps[stepIndex] + Int(fraction * Float(range)) + } + } + + init(realValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.maxRealValue = maxRealValue + self.maxSliderValue = maxSliderValue + self.isLogarithmic = isLogarithmic + + self.realValue = realValue + self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) + } + + init(sliderValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.maxRealValue = maxRealValue + self.maxSliderValue = maxSliderValue + self.isLogarithmic = isLogarithmic + + self.sliderValue = sliderValue + self.realValue = Amount.remapSliderToValue(sliderValue: self.sliderValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) + } + + func withRealValue(_ realValue: Int) -> Amount { + return Amount(realValue: realValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } + + func withSliderValue(_ sliderValue: Int) -> Amount { + return Amount(sliderValue: sliderValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } + } + final class View: UIView, UIScrollViewDelegate { private let dimView: UIView private let backgroundLayer: SimpleLayer @@ -959,7 +1044,9 @@ private final class ChatSendStarsScreenComponent: Component { private var topOffsetDistance: CGFloat? private var balance: Int64? - private var amount: Int64 = 1 + + private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true) + private var isAnonymous: Bool = false private var cachedStarImage: (UIImage, PresentationTheme)? private var cachedCloseImage: UIImage? @@ -1058,6 +1145,12 @@ private final class ChatSendStarsScreenComponent: Component { return result } + if let badgeView = self.badge.view, badgeView.hitTest(self.convert(point, to: badgeView), with: event) != nil { + if let sliderView = self.slider.view as? SliderComponent.View, let hitTestTarget = sliderView.hitTestTarget { + return hitTestTarget + } + } + let result = super.hitTest(point, with: event) return result } @@ -1135,7 +1228,11 @@ private final class ChatSendStarsScreenComponent: Component { if self.component == nil { self.balance = component.balance - self.amount = 50 + var isLogarithmic = true + if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_stars_reaction_logarithmic_scale"] as? Double { + isLogarithmic = Int(value) != 0 + } + self.amount = Amount(realValue: 50, maxRealValue: component.maxAmount, maxSliderValue: 999, isLogarithmic: isLogarithmic) if let myTopPeer = component.myTopPeer { self.isAnonymous = myTopPeer.isAnonymous } @@ -1182,8 +1279,8 @@ private final class ChatSendStarsScreenComponent: Component { let sliderSize = self.slider.update( transition: transition, component: AnyComponent(SliderComponent( - valueCount: component.maxAmount, - value: Int(self.amount), + valueCount: self.amount.maxSliderValue + 1, + value: self.amount.sliderValue, markPositions: false, trackBackgroundColor: .clear, trackForegroundColor: .clear, @@ -1193,7 +1290,8 @@ private final class ChatSendStarsScreenComponent: Component { guard let self, let component = self.component else { return } - self.amount = 1 + Int64(value) + self.amount = self.amount.withSliderValue(value) + self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint())) let sliderValue = Float(value) / Float(component.maxAmount) @@ -1250,7 +1348,7 @@ private final class ChatSendStarsScreenComponent: Component { let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight + 127.0), size: sliderSize) let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0)) - let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(component.maxAmount - 1) + let progressFraction: CGFloat = CGFloat(self.amount.sliderValue) / CGFloat(self.amount.maxSliderValue) let topOthersCount: Int? = component.topPeers.filter({ !$0.isMy }).max(by: { $0.count < $1.count })?.count var topCount: Int? @@ -1310,7 +1408,7 @@ private final class ChatSendStarsScreenComponent: Component { transition: transition, component: AnyComponent(BadgeComponent( theme: environment.theme, - title: "\(self.amount)", + title: "\(self.amount.realValue)", inertiaDirection: effectiveInertiaDirection )), environment: {}, @@ -1542,7 +1640,7 @@ private final class ChatSendStarsScreenComponent: Component { if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) { mappedTopPeers.remove(at: index) } - var myCount = Int(self.amount) + var myCount = Int(self.amount.realValue) if let myTopPeer = component.myTopPeer { myCount += myTopPeer.count } @@ -1747,7 +1845,7 @@ private final class ChatSendStarsScreenComponent: Component { self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme) } - let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount)").string + let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 { buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) @@ -1778,7 +1876,7 @@ private final class ChatSendStarsScreenComponent: Component { return } - if balance < self.amount { + if balance < self.amount.realValue { let _ = (component.context.engine.payments.starsTopUpOptions() |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] options in @@ -1789,7 +1887,7 @@ private final class ChatSendStarsScreenComponent: Component { return } - let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: component.peer.id, requiredStars: self.amount), completion: { result in + let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: component.peer.id, requiredStars: Int64(self.amount.realValue)), completion: { result in let _ = result //TODO:release }) @@ -1805,13 +1903,13 @@ private final class ChatSendStarsScreenComponent: Component { } let isBecomingTop: Bool if let topCount { - isBecomingTop = self.amount > topCount + isBecomingTop = self.amount.realValue > topCount } else { isBecomingTop = true } component.completion( - self.amount, + Int64(self.amount.realValue), self.isAnonymous, isBecomingTop, ChatSendStarsScreen.TransitionOut( diff --git a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift index 5c526ae4b7..e6b4756517 100644 --- a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift +++ b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift @@ -70,6 +70,10 @@ public final class SliderComponent: Component { private var component: SliderComponent? private weak var state: EmptyComponentState? + public var hitTestTarget: UIView? { + return self.sliderView + } + override public init(frame: CGRect) { super.init(frame: frame) }