mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various fixes
This commit is contained in:
parent
f8c872db55
commit
874fac0c63
@ -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" = "Your account was frozen for breaking Telegram's [Terms and Conditions]().";
|
||||||
"FrozenAccount.Violation.TextNew_URL" = "https://telegram.org/tos";
|
"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";
|
||||||
|
@ -322,6 +322,7 @@ public enum ResolvedUrl {
|
|||||||
case premiumMultiGift(reference: String?)
|
case premiumMultiGift(reference: String?)
|
||||||
case collectible(gift: StarGift.UniqueGift?)
|
case collectible(gift: StarGift.UniqueGift?)
|
||||||
case messageLink(link: TelegramResolvedMessageLink?)
|
case messageLink(link: TelegramResolvedMessageLink?)
|
||||||
|
case stars
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ResolveUrlResult {
|
public enum ResolveUrlResult {
|
||||||
|
@ -141,6 +141,7 @@ public enum StarsPurchasePurpose: Equatable {
|
|||||||
case upgradeStarGift(requiredStars: Int64)
|
case upgradeStarGift(requiredStars: Int64)
|
||||||
case transferStarGift(requiredStars: Int64)
|
case transferStarGift(requiredStars: Int64)
|
||||||
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
|
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
|
||||||
|
case buyStarGift(requiredStars: Int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct PremiumConfiguration {
|
public struct PremiumConfiguration {
|
||||||
|
@ -167,10 +167,12 @@ public func searchCountries(items: [((String, String), String, [Int])], query: S
|
|||||||
let componentsOne = item.0.0.components(separatedBy: " ")
|
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 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 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)
|
let tokens = stringTokens(string)
|
||||||
if matchStringTokens(tokens, with: queryTokens) {
|
if matchStringTokens(tokens, with: queryTokens) {
|
||||||
for code in item.2 {
|
for code in item.2 {
|
||||||
|
@ -1441,6 +1441,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
break
|
break
|
||||||
case .messageLink:
|
case .messageLink:
|
||||||
break
|
break
|
||||||
|
case .stars:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
@ -955,6 +955,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
titleTransition = .immediate
|
titleTransition = .immediate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let statusSpacing: CGFloat = 3.0
|
||||||
let titleSideInset: CGFloat = 6.0
|
let titleSideInset: CGFloat = 6.0
|
||||||
var titleFrame: CGRect
|
var titleFrame: CGRect
|
||||||
if size.height > 40.0 {
|
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)
|
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 += credibilityIconWidth
|
||||||
titleSize.width += verifiedIconWidth
|
titleSize.width += verifiedIconWidth
|
||||||
titleSize.width += statusIconWidth
|
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 activitySize = self.activityNode.updateLayout(CGSize(width: clearBounds.size.width - titleSideInset * 2.0, height: clearBounds.size.height), alignment: .center)
|
||||||
let titleInfoSpacing: CGFloat = 0.0
|
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)
|
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
|
||||||
nextIconX -= titleCredibilitySize.width
|
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)
|
self.titleStatusIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize)
|
||||||
nextIconX -= titleStatusSize.width
|
nextIconX -= titleStatusSize.width
|
||||||
|
@ -240,9 +240,20 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
mainController = controller
|
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(
|
let giftController = GiftViewScreen(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
subject: .uniqueGift(uniqueGift, state.peerId),
|
subject: .uniqueGift(uniqueGift, state.peerId),
|
||||||
|
allSubjects: allSubjects,
|
||||||
|
index: index,
|
||||||
buyGift: { slug, peerId in
|
buyGift: { slug, peerId in
|
||||||
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId) ?? .complete()
|
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId) ?? .complete()
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -825,6 +825,14 @@ public final class MediaScrubberComponent: Component {
|
|||||||
transition: transition
|
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 })
|
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))
|
transition.setFrame(view: self.cursorImageView, frame: CGRect(origin: .zero, size: self.cursorView.frame.size))
|
||||||
|
|
||||||
|
|
||||||
if let (coverPosition, coverImage) = component.cover {
|
if let (coverPosition, coverImage) = component.cover {
|
||||||
let imageSize = CGSize(width: 36.0, height: 36.0)
|
let imageSize = CGSize(width: 36.0, height: 36.0)
|
||||||
var animateFrame = false
|
var animateFrame = false
|
||||||
@ -964,6 +971,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
fileprivate let audioIconView: UIImageView
|
fileprivate let audioIconView: UIImageView
|
||||||
fileprivate let audioTitle = ComponentView<Empty>()
|
fileprivate let audioTitle = ComponentView<Empty>()
|
||||||
|
|
||||||
|
fileprivate let segmentsContainerView = UIView()
|
||||||
fileprivate var segmentTitles: [Int32: ComponentView<Empty>] = [:]
|
fileprivate var segmentTitles: [Int32: ComponentView<Empty>] = [:]
|
||||||
fileprivate var segmentLayers: [Int32: SimpleLayer] = [:]
|
fileprivate var segmentLayers: [Int32: SimpleLayer] = [:]
|
||||||
|
|
||||||
@ -1037,7 +1045,10 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
self.clippingView.addSubview(self.scrollView)
|
self.clippingView.addSubview(self.scrollView)
|
||||||
self.scrollView.addSubview(self.containerView)
|
self.scrollView.addSubview(self.containerView)
|
||||||
self.backgroundView.addSubview(self.vibrancyView)
|
self.backgroundView.addSubview(self.vibrancyView)
|
||||||
|
|
||||||
|
self.segmentsContainerView.clipsToBounds = true
|
||||||
|
self.segmentsContainerView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
|
||||||
self.addGestureRecognizer(tapGesture)
|
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(
|
private func updateThumbnailContainers(
|
||||||
scrubberSize: CGSize,
|
scrubberSize: CGSize,
|
||||||
availableSize: 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)))
|
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(
|
func update(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
style: MediaScrubberComponent.Style,
|
style: MediaScrubberComponent.Style,
|
||||||
@ -1281,6 +1322,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
if self.videoTransparentFramesContainer.superview == nil {
|
if self.videoTransparentFramesContainer.superview == nil {
|
||||||
self.containerView.addSubview(self.videoTransparentFramesContainer)
|
self.containerView.addSubview(self.videoTransparentFramesContainer)
|
||||||
self.containerView.addSubview(self.videoOpaqueFramesContainer)
|
self.containerView.addSubview(self.videoOpaqueFramesContainer)
|
||||||
|
self.containerView.addSubview(self.segmentsContainerView)
|
||||||
}
|
}
|
||||||
var previousFramesUpdateTimestamp: Double?
|
var previousFramesUpdateTimestamp: Double?
|
||||||
if let previousParams, case let .video(_, previousFramesUpdateTimestampValue) = previousParams.track.content {
|
if let previousParams, case let .video(_, previousFramesUpdateTimestampValue) = previousParams.track.content {
|
||||||
@ -1333,6 +1375,12 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.updateSegmentContainer(
|
||||||
|
scrubberSize: scrubberSize,
|
||||||
|
availableSize: availableSize,
|
||||||
|
transition: transition
|
||||||
|
)
|
||||||
|
|
||||||
var frameAspectRatio = 0.66
|
var frameAspectRatio = 0.66
|
||||||
if let image = frames.first, image.size.height > 0.0 {
|
if let image = frames.first, image.size.height > 0.0 {
|
||||||
frameAspectRatio = max(0.66, image.size.width / image.size.height)
|
frameAspectRatio = max(0.66, image.size.width / image.size.height)
|
||||||
@ -1488,9 +1536,8 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
self.backgroundView.update(size: containerFrame.size, transition: transition.containedViewLayoutTransition)
|
self.backgroundView.update(size: containerFrame.size, transition: transition.containedViewLayoutTransition)
|
||||||
transition.setFrame(view: self.vibrancyView, frame: CGRect(origin: .zero, size: containerFrame.size))
|
transition.setFrame(view: self.vibrancyView, frame: CGRect(origin: .zero, size: containerFrame.size))
|
||||||
transition.setFrame(view: self.vibrancyContainer, frame: CGRect(origin: .zero, size: containerFrame.size))
|
transition.setFrame(view: self.vibrancyContainer, frame: CGRect(origin: .zero, size: containerFrame.size))
|
||||||
|
|
||||||
var segmentCount = 0
|
var segmentCount = 0
|
||||||
var segmentOrigin: CGFloat = 0.0
|
|
||||||
var segmentWidth: CGFloat = 0.0
|
var segmentWidth: CGFloat = 0.0
|
||||||
if let segmentDuration {
|
if let segmentDuration {
|
||||||
if duration > segmentDuration {
|
if duration > segmentDuration {
|
||||||
@ -1499,17 +1546,15 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
segmentWidth = floorToScreenPixels(containerFrame.width * fraction)
|
segmentWidth = floorToScreenPixels(containerFrame.width * fraction)
|
||||||
}
|
}
|
||||||
if let trimRange = track.trimRange {
|
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
|
let actualSegmentCount = Int(ceil((trimRange.upperBound - trimRange.lowerBound) / segmentDuration)) - 1
|
||||||
segmentCount = min(actualSegmentCount, segmentCount)
|
segmentCount = min(actualSegmentCount, segmentCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let displaySegmentLabels = segmentWidth >= 30.0
|
||||||
|
|
||||||
var validIds = Set<Int32>()
|
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) {
|
for i in 0 ..< min(segmentCount, 2) {
|
||||||
let id = Int32(i)
|
let id = Int32(i)
|
||||||
validIds.insert(id)
|
validIds.insert(id)
|
||||||
@ -1530,7 +1575,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
self.segmentLayers[id] = segmentLayer
|
self.segmentLayers[id] = segmentLayer
|
||||||
self.segmentTitles[id] = segmentTitle
|
self.segmentTitles[id] = segmentTitle
|
||||||
|
|
||||||
self.containerView.layer.addSublayer(segmentLayer)
|
self.segmentsContainerView.layer.addSublayer(segmentLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(layer: segmentLayer, frame: segmentFrame)
|
transition.setFrame(layer: segmentLayer, frame: segmentFrame)
|
||||||
@ -1546,8 +1591,9 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
containerSize: containerFrame.size
|
containerSize: containerFrame.size
|
||||||
)
|
)
|
||||||
if let view = segmentTitle.view {
|
if let view = segmentTitle.view {
|
||||||
|
view.alpha = displaySegmentLabels ? 1.0 : 0.0
|
||||||
if view.superview == nil {
|
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))
|
segmentTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: segmentFrame.maxX + 2.0, y: 2.0), size: segmentTitleSize))
|
||||||
}
|
}
|
||||||
|
@ -571,10 +571,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let allSubjects: [GiftViewScreen.Subject] = (self.starsProducts ?? []).map { .profileGift(self.peerId, $0) }
|
||||||
|
let index = self.starsProducts?.firstIndex(where: { $0 == product }) ?? 0
|
||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let controller = GiftViewScreen(
|
let controller = GiftViewScreen(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
subject: .profileGift(self.peerId, product),
|
subject: .profileGift(self.peerId, product),
|
||||||
|
allSubjects: allSubjects,
|
||||||
|
index: index,
|
||||||
updateSavedToProfile: { [weak self] reference, added in
|
updateSavedToProfile: { [weak self] reference, added in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
@ -249,6 +249,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
|||||||
} else {
|
} else {
|
||||||
textString = strings.Stars_Purchase_SendGroupMessageInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
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
|
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
|
titleText = strings.Stars_Purchase_GetStars
|
||||||
case .gift:
|
case .gift:
|
||||||
titleText = strings.Stars_Purchase_GiftStars
|
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))
|
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1280,6 +1282,8 @@ private extension StarsPurchasePurpose {
|
|||||||
return requiredStars
|
return requiredStars
|
||||||
case let .sendMessage(_, requiredStars):
|
case let .sendMessage(_, requiredStars):
|
||||||
return requiredStars
|
return requiredStars
|
||||||
|
case let .buyStarGift(requiredStars):
|
||||||
|
return requiredStars
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
let closeButton = Child(Button.self)
|
let closeButton = Child(Button.self)
|
||||||
let title = Child(Text.self)
|
let title = Child(Text.self)
|
||||||
let amountSection = Child(ListSectionComponent.self)
|
let amountSection = Child(ListSectionComponent.self)
|
||||||
|
let amountAdditionalLabel = Child(MultilineTextComponent.self)
|
||||||
let button = Child(ButtonComponent.self)
|
let button = Child(ButtonComponent.self)
|
||||||
let balanceTitle = Child(MultilineTextComponent.self)
|
let balanceTitle = Child(MultilineTextComponent.self)
|
||||||
let balanceValue = Child(MultilineTextComponent.self)
|
let balanceValue = Child(MultilineTextComponent.self)
|
||||||
@ -100,7 +101,8 @@ private final class SheetContent: CombinedComponent {
|
|||||||
let titleString: String
|
let titleString: String
|
||||||
let amountTitle: String
|
let amountTitle: String
|
||||||
let amountPlaceholder: String
|
let amountPlaceholder: String
|
||||||
let amountLabel: String?
|
var amountLabel: String?
|
||||||
|
var amountRightLabel: String?
|
||||||
|
|
||||||
let minAmount: StarsAmount?
|
let minAmount: StarsAmount?
|
||||||
let maxAmount: StarsAmount?
|
let maxAmount: StarsAmount?
|
||||||
@ -116,7 +118,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
|
|
||||||
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||||
maxAmount = status.balances.availableBalance
|
maxAmount = status.balances.availableBalance
|
||||||
amountLabel = nil
|
|
||||||
case .accountWithdraw:
|
case .accountWithdraw:
|
||||||
titleString = environment.strings.Stars_Withdraw_Title
|
titleString = environment.strings.Stars_Withdraw_Title
|
||||||
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
|
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
|
||||||
@ -124,7 +125,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
|
|
||||||
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||||
maxAmount = state.balance
|
maxAmount = state.balance
|
||||||
amountLabel = nil
|
|
||||||
case .paidMedia:
|
case .paidMedia:
|
||||||
titleString = environment.strings.Stars_PaidContent_Title
|
titleString = environment.strings.Stars_PaidContent_Title
|
||||||
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
|
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 {
|
if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
|
||||||
let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
|
let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
|
||||||
amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
|
amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
|
||||||
} else {
|
|
||||||
amountLabel = nil
|
|
||||||
}
|
}
|
||||||
case .reaction:
|
case .reaction:
|
||||||
titleString = environment.strings.Stars_SendStars_Title
|
titleString = environment.strings.Stars_SendStars_Title
|
||||||
@ -146,7 +144,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
|
|
||||||
minAmount = StarsAmount(value: 1, nanos: 0)
|
minAmount = StarsAmount(value: 1, nanos: 0)
|
||||||
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||||
amountLabel = nil
|
|
||||||
case let .starGiftResell(update):
|
case let .starGiftResell(update):
|
||||||
titleString = update ? environment.strings.Stars_SellGift_EditTitle : environment.strings.Stars_SellGift_Title
|
titleString = update ? environment.strings.Stars_SellGift_EditTitle : environment.strings.Stars_SellGift_Title
|
||||||
amountTitle = environment.strings.Stars_SellGift_AmountTitle
|
amountTitle = environment.strings.Stars_SellGift_AmountTitle
|
||||||
@ -154,7 +151,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
|
|
||||||
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
|
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
|
||||||
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
|
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
|
||||||
amountLabel = nil
|
|
||||||
case let .paidMessages(_, minAmountValue, _, kind):
|
case let .paidMessages(_, minAmountValue, _, kind):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
switch kind {
|
switch kind {
|
||||||
@ -168,7 +164,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
|
|
||||||
minAmount = StarsAmount(value: minAmountValue, nanos: 0)
|
minAmount = StarsAmount(value: minAmountValue, nanos: 0)
|
||||||
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
|
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
|
||||||
amountLabel = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = title.update(
|
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 starsValue = Int32(floor(Float(value) * Float(resaleConfiguration.paidMessageCommissionPermille) / 1000.0))
|
||||||
let starsString = environment.strings.Stars_SellGift_AmountInfo_Stars(starsValue)
|
let starsString = environment.strings.Stars_SellGift_AmountInfo_Stars(starsValue)
|
||||||
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo(starsString).string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
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 {
|
} else {
|
||||||
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo("\(resaleConfiguration.paidMessageCommissionPermille / 10)%").string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo("\(resaleConfiguration.paidMessageCommissionPermille / 10)%").string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||||
}
|
}
|
||||||
@ -355,8 +355,17 @@ private final class SheetContent: CombinedComponent {
|
|||||||
.cornerRadius(10.0)
|
.cornerRadius(10.0)
|
||||||
)
|
)
|
||||||
contentSize.height += amountSection.size.height
|
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
|
contentSize.height += 32.0
|
||||||
|
|
||||||
let buttonString: String
|
let buttonString: String
|
||||||
if case .paidMedia = component.mode {
|
if case .paidMedia = component.mode {
|
||||||
buttonString = environment.strings.Stars_PaidContent_Create
|
buttonString = environment.strings.Stars_PaidContent_Create
|
||||||
|
@ -802,7 +802,6 @@ func openResolvedUrlImpl(
|
|||||||
}
|
}
|
||||||
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
|
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
//TODO:localize
|
|
||||||
let controller = UndoOverlayController(
|
let controller = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .universal(
|
content: .universal(
|
||||||
@ -810,8 +809,8 @@ func openResolvedUrlImpl(
|
|||||||
scale: 0.066,
|
scale: 0.066,
|
||||||
colors: [:],
|
colors: [:],
|
||||||
title: nil,
|
title: nil,
|
||||||
text: "You have enough stars at the moment.",
|
text: presentationData.strings.Stars_Purchase_EnoughStars,
|
||||||
customUndoText: "Buy Anyway",
|
customUndoText: presentationData.strings.Stars_Purchase_BuyAnyway,
|
||||||
timeout: nil
|
timeout: nil
|
||||||
),
|
),
|
||||||
elevatedLayout: true,
|
elevatedLayout: true,
|
||||||
@ -826,6 +825,12 @@ func openResolvedUrlImpl(
|
|||||||
proceed()
|
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):
|
case let .joinVoiceChat(peerId, invite):
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
@ -1016,7 +1016,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if parsedUrl.host == "importStickers" {
|
if parsedUrl.host == "stars" {
|
||||||
|
handleResolvedUrl(.stars)
|
||||||
|
} else if parsedUrl.host == "importStickers" {
|
||||||
handleResolvedUrl(.importStickers)
|
handleResolvedUrl(.importStickers)
|
||||||
} else if parsedUrl.host == "settings" {
|
} else if parsedUrl.host == "settings" {
|
||||||
if let path = parsedUrl.pathComponents.last {
|
if let path = parsedUrl.pathComponents.last {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user