Various fixes

This commit is contained in:
Ilya Laktyushin 2025-05-01 20:38:45 +04:00
parent f8c872db55
commit 874fac0c63
16 changed files with 1773 additions and 1531 deletions

View File

@ -14301,3 +14301,16 @@ Sorry for the inconvenience.";
"FrozenAccount.Violation.TextNew" = "Your account was frozen for breaking Telegram's [Terms and Conditions]().";
"FrozenAccount.Violation.TextNew_URL" = "https://telegram.org/tos";
"Stars.Purchase.BuyStarGiftInfo" = "Buy Stars to acquire a unique collectible.";
"Stars.Purchase.EnoughStars" = "You have enough stars at the moment.";
"Stars.Purchase.BuyAnyway" = "Buy Anyway";
"Gift.Buy.Confirm.Title" = "Confirm Payment";
"Gift.Buy.Confirm.Text" = "Do you really want to buy **%1$@** for %2$@?";
"Gift.Buy.Confirm.GiftText" = "Do you really want to buy **%1$@** for %2$@ and gift it to **%3$@**?";
"Gift.Buy.Confirm.Text.Stars_1" = "**%@** Star";
"Gift.Buy.Confirm.Text.Stars_any" = "**%@** Stars";
"Gift.Buy.Confirm.BuyFor_1" = "Buy for %@ Star";
"Gift.Buy.Confirm.BuyFor_any" = "Buy for %@ Stars";

View File

@ -322,6 +322,7 @@ public enum ResolvedUrl {
case premiumMultiGift(reference: String?)
case collectible(gift: StarGift.UniqueGift?)
case messageLink(link: TelegramResolvedMessageLink?)
case stars
}
public enum ResolveUrlResult {

View File

@ -141,6 +141,7 @@ public enum StarsPurchasePurpose: Equatable {
case upgradeStarGift(requiredStars: Int64)
case transferStarGift(requiredStars: Int64)
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
case buyStarGift(requiredStars: Int64)
}
public struct PremiumConfiguration {

View File

@ -167,10 +167,12 @@ public func searchCountries(items: [((String, String), String, [Int])], query: S
let componentsOne = item.0.0.components(separatedBy: " ")
let abbrOne = componentsOne.compactMap { $0.first.flatMap { String($0) } }.reduce(into: String(), { $0.append(contentsOf: $1) }).replacingOccurrences(of: "&", with: "")
let componentsTwo = item.0.0.components(separatedBy: " ")
let componentsTwo = item.0.1.components(separatedBy: " ")
let abbrTwo = componentsTwo.compactMap { $0.first.flatMap { String($0) } }.reduce(into: String(), { $0.append(contentsOf: $1) }).replacingOccurrences(of: "&", with: "")
let string = "\(item.0.0) \((item.0.1)) \(item.1) \(abbrOne) \(abbrTwo)"
let phoneCodes = item.2.map { "\($0)" }.joined(separator: " ")
let string = "\(item.0.0) \((item.0.1)) \(item.1) \(abbrOne) \(abbrTwo) \(phoneCodes)"
let tokens = stringTokens(string)
if matchStringTokens(tokens, with: queryTokens) {
for code in item.2 {

View File

@ -1441,6 +1441,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .messageLink:
break
case .stars:
break
}
}
}))

View File

@ -955,6 +955,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
titleTransition = .immediate
}
let statusSpacing: CGFloat = 3.0
let titleSideInset: CGFloat = 6.0
var titleFrame: CGRect
if size.height > 40.0 {
@ -966,7 +967,12 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), insets: titleInsets, animated: titleTransition.isAnimated)
titleSize.width += credibilityIconWidth
titleSize.width += verifiedIconWidth
if statusIconWidth > 0.0 {
titleSize.width += statusIconWidth
if credibilityIconWidth > 0.0 {
titleSize.width += statusSpacing
}
}
let activitySize = self.activityNode.updateLayout(CGSize(width: clearBounds.size.width - titleSideInset * 2.0, height: clearBounds.size.height), alignment: .center)
let titleInfoSpacing: CGFloat = 0.0
@ -1006,6 +1012,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
nextIconX -= titleCredibilitySize.width
if credibilityIconWidth > 0.0 {
nextIconX -= statusSpacing
}
self.titleStatusIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize)
nextIconX -= titleStatusSize.width

View File

@ -240,9 +240,20 @@ final class GiftStoreScreenComponent: Component {
} else {
mainController = controller
}
let allSubjects: [GiftViewScreen.Subject] = (self.effectiveGifts ?? []).compactMap { gift in
if case let .unique(uniqueGift) = gift {
return .uniqueGift(uniqueGift, state.peerId)
}
return nil
}
let index = self.effectiveGifts?.firstIndex(where: { $0 == .unique(uniqueGift) }) ?? 0
let giftController = GiftViewScreen(
context: component.context,
subject: .uniqueGift(uniqueGift, state.peerId),
allSubjects: allSubjects,
index: index,
buyGift: { slug, peerId in
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId) ?? .complete()
},

View File

@ -0,0 +1,241 @@
import Foundation
import UIKit
import ComponentFlow
import Display
import TelegramPresentationData
import ViewControllerComponent
import AccountContext
final class GiftPagerComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
public final class Item: Equatable {
let id: AnyHashable
let subject: GiftViewScreen.Subject
public init(id: AnyHashable, subject: GiftViewScreen.Subject) {
self.id = id
self.subject = subject
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.subject != rhs.subject {
return false
}
return true
}
}
let context: AccountContext
let items: [Item]
let index: Int
let itemSpacing: CGFloat
let updated: (CGFloat, Int) -> Void
public init(
context: AccountContext,
items: [Item],
index: Int = 0,
itemSpacing: CGFloat = 0.0,
updated: @escaping (CGFloat, Int) -> Void
) {
self.context = context
self.items = items
self.index = index
self.itemSpacing = itemSpacing
self.updated = updated
}
public static func ==(lhs: GiftPagerComponent, rhs: GiftPagerComponent) -> Bool {
if lhs.items != rhs.items {
return false
}
if lhs.index != rhs.index {
return false
}
if lhs.itemSpacing != rhs.itemSpacing {
return false
}
return true
}
final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView
private var itemViews: [AnyHashable: ComponentHostView<EnvironmentType>] = [:]
private var component: GiftPagerComponent?
private var environment: Environment<EnvironmentType>?
override init(frame: CGRect) {
self.scrollView = UIScrollView(frame: frame)
self.scrollView.isPagingEnabled = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.bounces = false
self.scrollView.layer.cornerRadius = 10.0
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
super.init(frame: frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var isSwiping: Bool = false
private var lastScrollTime: TimeInterval = 0
private let swipeInactiveThreshold: TimeInterval = 0.5
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.isSwiping = true
self.lastScrollTime = CACurrentMediaTime()
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.isSwiping = false
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.isSwiping = false
}
private var ignoreContentOffsetChange = false
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let component = self.component, let environment = self.environment, !self.ignoreContentOffsetChange && !self.isUpdating else {
return
}
if self.isSwiping {
self.lastScrollTime = CACurrentMediaTime()
}
self.ignoreContentOffsetChange = true
let _ = self.update(component: component, availableSize: self.bounds.size, environment: environment, transition: .immediate)
component.updated(self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width), component.items.count)
self.ignoreContentOffsetChange = false
}
private var isUpdating = true
func update(component: GiftPagerComponent, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
var validIds: [AnyHashable] = []
self.component = component
self.environment = environment
let firstTime = self.itemViews.isEmpty
let itemWidth = availableSize.width
let totalWidth = itemWidth * CGFloat(component.items.count) + component.itemSpacing * CGFloat(max(0, component.items.count - 1))
let contentSize = CGSize(width: totalWidth, height: availableSize.height)
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
}
let scrollFrame = CGRect(origin: .zero, size: availableSize)
if self.scrollView.frame != scrollFrame {
self.scrollView.frame = scrollFrame
}
if firstTime {
let initialOffset = CGFloat(component.index) * (itemWidth + component.itemSpacing)
self.scrollView.contentOffset = CGPoint(x: initialOffset, y: 0.0)
var position: CGFloat
if self.scrollView.contentSize.width > self.scrollView.frame.width {
position = self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width)
} else {
position = 0.0
}
component.updated(position, component.items.count)
}
let viewportCenter = self.scrollView.contentOffset.x + availableSize.width * 0.5
let currentTime = CACurrentMediaTime()
let isSwipingActive = self.isSwiping || (currentTime - self.lastScrollTime < self.swipeInactiveThreshold)
var i = 0
for item in component.items {
let itemOriginX = (itemWidth + component.itemSpacing) * CGFloat(i)
let itemFrame = CGRect(origin: CGPoint(x: itemOriginX, y: 0.0), size: CGSize(width: itemWidth, height: availableSize.height))
let centerDelta = itemFrame.midX - viewportCenter
let position = centerDelta / (availableSize.width * 0.75)
i += 1
if !isSwipingActive && abs(position) > 0.5 {
continue
} else if isSwipingActive && abs(position) > 1.5 {
continue
}
validIds.append(item.id)
let itemView: ComponentHostView<EnvironmentType>
var itemTransition = transition
if let current = self.itemViews[item.id] {
itemView = current
} else {
itemTransition = transition.withAnimation(.none)
itemView = ComponentHostView<EnvironmentType>()
self.itemViews[item.id] = itemView
self.scrollView.addSubview(itemView)
}
let environment = environment[EnvironmentType.self]
let _ = itemView.update(
transition: itemTransition,
component: AnyComponent(GiftViewSheetComponent(
context: component.context,
subject: item.subject
)),
environment: { environment },
containerSize: availableSize
)
itemView.frame = itemFrame
}
var removeIds: [AnyHashable] = []
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {
removeIds.append(id)
itemView.removeFromSuperview()
}
}
for id in removeIds {
self.itemViews.removeValue(forKey: id)
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,278 @@
import Foundation
import UIKit
import ComponentFlow
import Display
import TelegramPresentationData
import MultilineTextComponent
final class TableComponent: CombinedComponent {
class Item: Equatable {
public let id: AnyHashable
public let title: String?
public let hasBackground: Bool
public let component: AnyComponent<Empty>
public let insets: UIEdgeInsets?
public init<IdType: Hashable>(id: IdType, title: String?, hasBackground: Bool = false, component: AnyComponent<Empty>, insets: UIEdgeInsets? = nil) {
self.id = AnyHashable(id)
self.title = title
self.hasBackground = hasBackground
self.component = component
self.insets = insets
}
public static func == (lhs: Item, rhs: Item) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.hasBackground != rhs.hasBackground {
return false
}
if lhs.component != rhs.component {
return false
}
if lhs.insets != rhs.insets {
return false
}
return true
}
}
private let theme: PresentationTheme
private let items: [Item]
public init(theme: PresentationTheme, items: [Item]) {
self.theme = theme
self.items = items
}
public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.items != rhs.items {
return false
}
return true
}
final class State: ComponentState {
var cachedBorderImage: (UIImage, PresentationTheme)?
}
func makeState() -> State {
return State()
}
public static var body: Body {
let leftColumnBackground = Child(Rectangle.self)
let lastBackground = Child(Rectangle.self)
let verticalBorder = Child(Rectangle.self)
let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let outerBorder = Child(Image.self)
return { context in
let verticalPadding: CGFloat = 11.0
let horizontalPadding: CGFloat = 12.0
let borderWidth: CGFloat = 1.0
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
let secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor
var leftColumnWidth: CGFloat = 0.0
var updatedTitleChildren: [Int: _UpdatedChildComponent] = [:]
var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = []
var updatedBorderChildren: [_UpdatedChildComponent] = []
var i = 0
for item in context.component.items {
guard let title = item.title else {
i += 1
continue
}
let titleChild = titleChildren[item.id].update(
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor))
)),
availableSize: context.availableSize,
transition: context.transition
)
updatedTitleChildren[i] = titleChild
if titleChild.size.width > leftColumnWidth {
leftColumnWidth = titleChild.size.width
}
i += 1
}
leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0)
let rightColumnWidth = context.availableSize.width - leftColumnWidth
i = 0
var rowHeights: [Int: CGFloat] = [:]
var totalHeight: CGFloat = 0.0
var innerTotalHeight: CGFloat = 0.0
var hasLastBackground = false
for item in context.component.items {
let insets: UIEdgeInsets
if let customInsets = item.insets {
insets = customInsets
} else {
insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding)
}
var titleHeight: CGFloat = 0.0
if let titleChild = updatedTitleChildren[i] {
titleHeight = titleChild.size.height
}
let availableValueWidth: CGFloat
if titleHeight > 0.0 {
availableValueWidth = rightColumnWidth
} else {
availableValueWidth = context.availableSize.width
}
let valueChild = valueChildren[item.id].update(
component: item.component,
availableSize: CGSize(width: availableValueWidth - insets.left - insets.right, height: context.availableSize.height),
transition: context.transition
)
updatedValueChildren.append((valueChild, insets))
let rowHeight = max(40.0, max(titleHeight, valueChild.size.height) + verticalPadding * 2.0)
rowHeights[i] = rowHeight
totalHeight += rowHeight
if titleHeight > 0.0 {
innerTotalHeight += rowHeight
}
if i < context.component.items.count - 1 {
let borderChild = borderChildren[item.id].update(
component: AnyComponent(Rectangle(color: borderColor)),
availableSize: CGSize(width: context.availableSize.width, height: borderWidth),
transition: context.transition
)
updatedBorderChildren.append(borderChild)
}
if item.hasBackground {
hasLastBackground = true
}
i += 1
}
if hasLastBackground {
let lastRowHeight = rowHeights[i - 1] ?? 0
let lastBackground = lastBackground.update(
component: Rectangle(color: secondaryBackgroundColor),
availableSize: CGSize(width: context.availableSize.width, height: lastRowHeight),
transition: context.transition
)
context.add(
lastBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight - lastRowHeight / 2.0))
)
}
let leftColumnBackground = leftColumnBackground.update(
component: Rectangle(color: secondaryBackgroundColor),
availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight),
transition: context.transition
)
context.add(
leftColumnBackground
.position(CGPoint(x: leftColumnWidth / 2.0, y: innerTotalHeight / 2.0))
)
let borderImage: UIImage
if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme {
borderImage = currentImage
} else {
let borderRadius: CGFloat = 10.0
borderImage = generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.setFillColor(backgroundColor.cgColor)
context.fill(bounds)
let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil)
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
context.setBlendMode(.normal)
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.addPath(path)
context.strokePath()
})!.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10)
context.state.cachedBorderImage = (borderImage, context.component.theme)
}
let outerBorder = outerBorder.update(
component: Image(image: borderImage),
availableSize: CGSize(width: context.availableSize.width, height: totalHeight),
transition: context.transition
)
context.add(outerBorder
.position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0))
)
let verticalBorder = verticalBorder.update(
component: Rectangle(color: borderColor),
availableSize: CGSize(width: borderWidth, height: innerTotalHeight),
transition: context.transition
)
context.add(
verticalBorder
.position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: innerTotalHeight / 2.0))
)
i = 0
var originY: CGFloat = 0.0
for (valueChild, valueInsets) in updatedValueChildren {
let rowHeight = rowHeights[i] ?? 0.0
let valueFrame: CGRect
if let titleChild = updatedTitleChildren[i] {
let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size)
context.add(titleChild
.position(titleFrame.center)
)
valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size)
} else {
if hasLastBackground {
valueFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - valueChild.size.width) / 2.0), y: originY + verticalPadding), size: valueChild.size)
} else {
valueFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: valueChild.size)
}
}
context.add(valueChild
.position(valueFrame.center)
)
if i < updatedBorderChildren.count {
let borderChild = updatedBorderChildren[i]
context.add(borderChild
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0))
)
}
originY += rowHeight
i += 1
}
return CGSize(width: context.availableSize.width, height: totalHeight)
}
}
}

View File

@ -825,6 +825,14 @@ public final class MediaScrubberComponent: Component {
transition: transition
)
}
} else {
for (_ , trackView) in self.trackViews {
trackView.updateTrimEdges(
left: leftHandleFrame.minX,
right: rightHandleFrame.maxX,
transition: transition
)
}
}
let isDraggingTracks = self.trackViews.values.contains(where: { $0.isDragging })
@ -863,7 +871,6 @@ public final class MediaScrubberComponent: Component {
transition.setFrame(view: self.cursorImageView, frame: CGRect(origin: .zero, size: self.cursorView.frame.size))
if let (coverPosition, coverImage) = component.cover {
let imageSize = CGSize(width: 36.0, height: 36.0)
var animateFrame = false
@ -964,6 +971,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
fileprivate let audioIconView: UIImageView
fileprivate let audioTitle = ComponentView<Empty>()
fileprivate let segmentsContainerView = UIView()
fileprivate var segmentTitles: [Int32: ComponentView<Empty>] = [:]
fileprivate var segmentLayers: [Int32: SimpleLayer] = [:]
@ -1038,6 +1046,9 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
self.scrollView.addSubview(self.containerView)
self.backgroundView.addSubview(self.vibrancyView)
self.segmentsContainerView.clipsToBounds = true
self.segmentsContainerView.isUserInteractionEnabled = false
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
self.addGestureRecognizer(tapGesture)
@ -1133,6 +1144,25 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
}
}
private var leftTrimEdge: CGFloat?
private var rightTrimEdge: CGFloat?
func updateTrimEdges(
left: CGFloat,
right: CGFloat,
transition: ComponentTransition
) {
self.leftTrimEdge = left
self.rightTrimEdge = right
if let params = self.params {
self.updateSegmentContainer(
scrubberSize: CGSize(width: params.availableSize.width, height: trackHeight),
availableSize: params.availableSize,
transition: transition
)
}
}
private func updateThumbnailContainers(
scrubberSize: CGSize,
availableSize: CGSize,
@ -1146,6 +1176,17 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
transition.setBounds(view: self.videoOpaqueFramesContainer, bounds: CGRect(origin: CGPoint(x: containerLeftEdge, y: 0.0), size: CGSize(width: containerRightEdge - containerLeftEdge, height: scrubberSize.height)))
}
private func updateSegmentContainer(
scrubberSize: CGSize,
availableSize: CGSize,
transition: ComponentTransition
) {
let containerLeftEdge: CGFloat = self.leftTrimEdge ?? 0.0
let containerRightEdge: CGFloat = self.rightTrimEdge ?? availableSize.width
transition.setFrame(view: self.segmentsContainerView, frame: CGRect(origin: CGPoint(x: containerLeftEdge, y: 0.0), size: CGSize(width: containerRightEdge - containerLeftEdge - 2.0, height: scrubberSize.height)))
}
func update(
context: AccountContext,
style: MediaScrubberComponent.Style,
@ -1281,6 +1322,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
if self.videoTransparentFramesContainer.superview == nil {
self.containerView.addSubview(self.videoTransparentFramesContainer)
self.containerView.addSubview(self.videoOpaqueFramesContainer)
self.containerView.addSubview(self.segmentsContainerView)
}
var previousFramesUpdateTimestamp: Double?
if let previousParams, case let .video(_, previousFramesUpdateTimestampValue) = previousParams.track.content {
@ -1333,6 +1375,12 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
transition: transition
)
self.updateSegmentContainer(
scrubberSize: scrubberSize,
availableSize: availableSize,
transition: transition
)
var frameAspectRatio = 0.66
if let image = frames.first, image.size.height > 0.0 {
frameAspectRatio = max(0.66, image.size.width / image.size.height)
@ -1490,7 +1538,6 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
transition.setFrame(view: self.vibrancyContainer, frame: CGRect(origin: .zero, size: containerFrame.size))
var segmentCount = 0
var segmentOrigin: CGFloat = 0.0
var segmentWidth: CGFloat = 0.0
if let segmentDuration {
if duration > segmentDuration {
@ -1499,17 +1546,15 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
segmentWidth = floorToScreenPixels(containerFrame.width * fraction)
}
if let trimRange = track.trimRange {
if trimRange.lowerBound > 0.0 {
let fraction = trimRange.lowerBound / duration
segmentOrigin = floorToScreenPixels(containerFrame.width * fraction)
}
let actualSegmentCount = Int(ceil((trimRange.upperBound - trimRange.lowerBound) / segmentDuration)) - 1
segmentCount = min(actualSegmentCount, segmentCount)
}
}
let displaySegmentLabels = segmentWidth >= 30.0
var validIds = Set<Int32>()
var segmentFrame = CGRect(x: segmentOrigin + segmentWidth, y: 0.0, width: 1.0, height: containerFrame.size.height)
var segmentFrame = CGRect(x: segmentWidth, y: 0.0, width: 1.0, height: containerFrame.size.height)
for i in 0 ..< min(segmentCount, 2) {
let id = Int32(i)
validIds.insert(id)
@ -1530,7 +1575,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
self.segmentLayers[id] = segmentLayer
self.segmentTitles[id] = segmentTitle
self.containerView.layer.addSublayer(segmentLayer)
self.segmentsContainerView.layer.addSublayer(segmentLayer)
}
transition.setFrame(layer: segmentLayer, frame: segmentFrame)
@ -1546,8 +1591,9 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
containerSize: containerFrame.size
)
if let view = segmentTitle.view {
view.alpha = displaySegmentLabels ? 1.0 : 0.0
if view.superview == nil {
self.containerView.addSubview(view)
self.segmentsContainerView.addSubview(view)
}
segmentTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: segmentFrame.maxX + 2.0, y: 2.0), size: segmentTitleSize))
}

View File

@ -571,10 +571,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}
}
} else {
let allSubjects: [GiftViewScreen.Subject] = (self.starsProducts ?? []).map { .profileGift(self.peerId, $0) }
let index = self.starsProducts?.firstIndex(where: { $0 == product }) ?? 0
var dismissImpl: (() -> Void)?
let controller = GiftViewScreen(
context: self.context,
subject: .profileGift(self.peerId, product),
allSubjects: allSubjects,
index: index,
updateSavedToProfile: { [weak self] reference, added in
guard let self else {
return

View File

@ -249,6 +249,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
} else {
textString = strings.Stars_Purchase_SendGroupMessageInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
}
case .buyStarGift:
textString = strings.Stars_Purchase_BuyStarGiftInfo
}
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in
@ -830,7 +832,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent {
titleText = strings.Stars_Purchase_GetStars
case .gift:
titleText = strings.Stars_Purchase_GiftStars
case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars):
case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars), let .buyStarGift(requiredStars):
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
}
@ -1280,6 +1282,8 @@ private extension StarsPurchasePurpose {
return requiredStars
case let .sendMessage(_, requiredStars):
return requiredStars
case let .buyStarGift(requiredStars):
return requiredStars
default:
return nil
}

View File

@ -55,6 +55,7 @@ private final class SheetContent: CombinedComponent {
let closeButton = Child(Button.self)
let title = Child(Text.self)
let amountSection = Child(ListSectionComponent.self)
let amountAdditionalLabel = Child(MultilineTextComponent.self)
let button = Child(ButtonComponent.self)
let balanceTitle = Child(MultilineTextComponent.self)
let balanceValue = Child(MultilineTextComponent.self)
@ -100,7 +101,8 @@ private final class SheetContent: CombinedComponent {
let titleString: String
let amountTitle: String
let amountPlaceholder: String
let amountLabel: String?
var amountLabel: String?
var amountRightLabel: String?
let minAmount: StarsAmount?
let maxAmount: StarsAmount?
@ -116,7 +118,6 @@ private final class SheetContent: CombinedComponent {
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
maxAmount = status.balances.availableBalance
amountLabel = nil
case .accountWithdraw:
titleString = environment.strings.Stars_Withdraw_Title
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
@ -124,7 +125,6 @@ private final class SheetContent: CombinedComponent {
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
maxAmount = state.balance
amountLabel = nil
case .paidMedia:
titleString = environment.strings.Stars_PaidContent_Title
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
@ -136,8 +136,6 @@ private final class SheetContent: CombinedComponent {
if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
amountLabel = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
} else {
amountLabel = nil
}
case .reaction:
titleString = environment.strings.Stars_SendStars_Title
@ -146,7 +144,6 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: 1, nanos: 0)
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
amountLabel = nil
case let .starGiftResell(update):
titleString = update ? environment.strings.Stars_SellGift_EditTitle : environment.strings.Stars_SellGift_Title
amountTitle = environment.strings.Stars_SellGift_AmountTitle
@ -154,7 +151,6 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
amountLabel = nil
case let .paidMessages(_, minAmountValue, _, kind):
//TODO:localize
switch kind {
@ -168,7 +164,6 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: minAmountValue, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
amountLabel = nil
}
let title = title.update(
@ -287,6 +282,11 @@ private final class SheetContent: CombinedComponent {
let starsValue = Int32(floor(Float(value) * Float(resaleConfiguration.paidMessageCommissionPermille) / 1000.0))
let starsString = environment.strings.Stars_SellGift_AmountInfo_Stars(starsValue)
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo(starsString).string, attributes: amountMarkdownAttributes, textAlignment: .natural))
if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate {
let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
amountRightLabel = "\(formatTonUsdValue(Int64(starsValue), divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
}
} else {
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo("\(resaleConfiguration.paidMessageCommissionPermille / 10)%").string, attributes: amountMarkdownAttributes, textAlignment: .natural))
}
@ -355,6 +355,15 @@ private final class SheetContent: CombinedComponent {
.cornerRadius(10.0)
)
contentSize.height += amountSection.size.height
if let amountRightLabel {
let amountAdditionalLabel = amountAdditionalLabel.update(
component: MultilineTextComponent(text: .plain(NSAttributedString(string: amountRightLabel, font: amountFont, textColor: amountTextColor))),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
transition: context.transition
)
context.add(amountAdditionalLabel
.position(CGPoint(x: context.availableSize.width - amountAdditionalLabel.size.width / 2.0 - sideInset - 16.0, y: contentSize.height - amountAdditionalLabel.size.height / 2.0)))
}
contentSize.height += 32.0
let buttonString: String

View File

@ -802,7 +802,6 @@ func openResolvedUrlImpl(
}
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let controller = UndoOverlayController(
presentationData: presentationData,
content: .universal(
@ -810,8 +809,8 @@ func openResolvedUrlImpl(
scale: 0.066,
colors: [:],
title: nil,
text: "You have enough stars at the moment.",
customUndoText: "Buy Anyway",
text: presentationData.strings.Stars_Purchase_EnoughStars,
customUndoText: presentationData.strings.Stars_Purchase_BuyAnyway,
timeout: nil
),
elevatedLayout: true,
@ -826,6 +825,12 @@ func openResolvedUrlImpl(
proceed()
}
}
case .stars:
dismissInput()
let controller = context.sharedContext.makeStarsIntroScreen(context: context)
if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true)
}
case let .joinVoiceChat(peerId, invite):
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in

View File

@ -1016,7 +1016,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
}
}
} else {
if parsedUrl.host == "importStickers" {
if parsedUrl.host == "stars" {
handleResolvedUrl(.stars)
} else if parsedUrl.host == "importStickers" {
handleResolvedUrl(.importStickers)
} else if parsedUrl.host == "settings" {
if let path = parsedUrl.pathComponents.last {