mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-08-10 08:10:32 +02:00
chore: migrate to new version + fixed several critical bugs
- Migrated project to latest Telegram iOS base (v12.3.2+) - Fixed circular dependency between GhostModeManager and MiscSettingsManager - Fixed multiple Bazel build configuration errors (select() default conditions) - Fixed duplicate type definitions in PeerInfoScreen - Fixed swiftmodule directory resolution in build scripts - Added Ghostgram Settings tab in main Settings menu with all 5 features - Cleared sensitive credentials from config.json (template-only now) - Excluded bazel-cache from version control
This commit is contained in:
+4
-2
@@ -19,6 +19,8 @@ import TelegramStringFormatting
|
||||
import GlassBarButtonComponent
|
||||
import GiftItemComponent
|
||||
import EdgeEffect
|
||||
import TableComponent
|
||||
import PeerTableCellComponent
|
||||
|
||||
private final class GiftAuctionAcquiredScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@@ -373,7 +375,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
||||
title: environment.strings.Gift_Acquired_Recipient,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
@@ -472,7 +474,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: environment.theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
|
||||
+6
-7
@@ -372,7 +372,7 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
||||
if self.backgroundHandleView.image == nil {
|
||||
self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.backgroundHandleView.tintColor = environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(environment.theme.overallDarkAppearance ? 0.2 : 0.07)
|
||||
self.backgroundHandleView.tintColor = theme.list.itemPrimaryTextColor.withMultipliedAlpha(theme.overallDarkAppearance ? 0.2 : 0.07)
|
||||
let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0))
|
||||
if self.backgroundHandleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(self.backgroundHandleView)
|
||||
@@ -383,13 +383,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
@@ -413,14 +413,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
contentHeight += environment.safeInsets.bottom
|
||||
|
||||
var initialContentHeight = contentHeight
|
||||
let clippingY: CGFloat
|
||||
|
||||
let titleText: String = environment.strings.Gift_ActiveAuctions_Title(Int32(self.auctionStates.count))
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
@@ -434,12 +433,12 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
|
||||
initialContentHeight = contentHeight
|
||||
let initialContentHeight = contentHeight
|
||||
|
||||
let edgeEffectHeight: CGFloat = 80.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.topEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
self.topEdgeEffectView.update(content: theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
if self.topEdgeEffectView.superview == nil {
|
||||
self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0)
|
||||
}
|
||||
|
||||
+57
-13
@@ -18,7 +18,6 @@ import RoundedRectWithTailPath
|
||||
import AvatarNode
|
||||
import BundleIconComponent
|
||||
import TextFormat
|
||||
import CheckComponent
|
||||
import ContextUI
|
||||
import StarsBalanceOverlayComponent
|
||||
import StoryLiveChatMessageComponent
|
||||
@@ -369,17 +368,20 @@ private final class PeerPlaceComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let color: UIColor
|
||||
let place: Int32?
|
||||
let placeIsApproximate: Bool
|
||||
let groupingSeparator: String
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
color: UIColor,
|
||||
place: Int32?,
|
||||
placeIsApproximate: Bool,
|
||||
groupingSeparator: String
|
||||
) {
|
||||
self.theme = theme
|
||||
self.color = color
|
||||
self.place = place
|
||||
self.placeIsApproximate = placeIsApproximate
|
||||
self.groupingSeparator = groupingSeparator
|
||||
}
|
||||
|
||||
@@ -393,6 +395,9 @@ private final class PeerPlaceComponent: Component {
|
||||
if lhs.place != rhs.place {
|
||||
return false
|
||||
}
|
||||
if lhs.placeIsApproximate != rhs.placeIsApproximate {
|
||||
return false
|
||||
}
|
||||
if lhs.groupingSeparator != rhs.groupingSeparator {
|
||||
return false
|
||||
}
|
||||
@@ -455,7 +460,7 @@ private final class PeerPlaceComponent: Component {
|
||||
var placeString: String
|
||||
if let place = component.place {
|
||||
placeString = presentationStringsFormattedNumber(place, component.groupingSeparator)
|
||||
if place >= 100 {
|
||||
if component.placeIsApproximate {
|
||||
placeString = "\(compactNumericCountString(Int(place), decimalSeparator: ".", showDecimalPart: false))+"
|
||||
}
|
||||
} else {
|
||||
@@ -501,6 +506,7 @@ private final class PeerComponent: Component {
|
||||
let groupingSeparator: String
|
||||
let peer: EnginePeer
|
||||
let place: Int32
|
||||
let placeIsApproximate: Bool
|
||||
let amount: Int64
|
||||
let status: Status?
|
||||
let isLast: Bool
|
||||
@@ -512,6 +518,7 @@ private final class PeerComponent: Component {
|
||||
groupingSeparator: String,
|
||||
peer: EnginePeer,
|
||||
place: Int32,
|
||||
placeIsApproximate: Bool,
|
||||
amount: Int64,
|
||||
status: Status? = nil,
|
||||
isLast: Bool,
|
||||
@@ -522,6 +529,7 @@ private final class PeerComponent: Component {
|
||||
self.groupingSeparator = groupingSeparator
|
||||
self.peer = peer
|
||||
self.place = place
|
||||
self.placeIsApproximate = placeIsApproximate
|
||||
self.amount = amount
|
||||
self.status = status
|
||||
self.isLast = isLast
|
||||
@@ -541,6 +549,9 @@ private final class PeerComponent: Component {
|
||||
if lhs.place != rhs.place {
|
||||
return false
|
||||
}
|
||||
if lhs.placeIsApproximate != rhs.placeIsApproximate {
|
||||
return false
|
||||
}
|
||||
if lhs.amount != rhs.amount {
|
||||
return false
|
||||
}
|
||||
@@ -623,8 +634,17 @@ private final class PeerComponent: Component {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let placeSize = self.place.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PeerPlaceComponent(theme: component.theme, color: color, place: component.status == .returned ? nil : component.place, groupingSeparator: presentationData.dateTimeFormat.groupingSeparator)),
|
||||
environment: {},
|
||||
component: AnyComponent(
|
||||
PeerPlaceComponent(
|
||||
theme: component.theme,
|
||||
color: color,
|
||||
place: component.status == .returned ? nil : component.place,
|
||||
placeIsApproximate: component.placeIsApproximate,
|
||||
groupingSeparator: presentationData.dateTimeFormat.groupingSeparator
|
||||
)
|
||||
),
|
||||
environment: {
|
||||
},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let placeFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - placeSize.height) / 2.0)), size: placeSize)
|
||||
@@ -1616,8 +1636,10 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
|
||||
component.context.starsContext?.load(force: true)
|
||||
|
||||
Queue.mainQueue().after(2.5) {
|
||||
component.context.starsContext?.load(force: true)
|
||||
}
|
||||
}, error: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
@@ -2061,7 +2083,7 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
if case .finished = auctionState?.auctionState, let controller = self.environment?.controller() {
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
controller.dismiss()
|
||||
let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: { _, _ in })
|
||||
let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, peerId: nil, completion: { _, _ in })
|
||||
navigationController.pushViewController(auctionController)
|
||||
}
|
||||
}
|
||||
@@ -2213,8 +2235,9 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
isBiddingUp = false
|
||||
}
|
||||
|
||||
place = giftAuctionState.getPlace(myBid: myBidAmount, myBidDate: myBidDate) ?? 1
|
||||
|
||||
let placeAndIsApproximate = giftAuctionState.getPlace(myBid: myBidAmount, myBidDate: myBidDate) ?? (1, false)
|
||||
place = placeAndIsApproximate.place
|
||||
|
||||
var bidTitle: String
|
||||
var bidTitleColor: UIColor
|
||||
var bidStatus: PeerComponent.Status?
|
||||
@@ -2247,7 +2270,18 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
|
||||
if let peer = self.peersMap[component.context.account.peerId] {
|
||||
myBidTitleComponent = AnyComponent(PeerHeaderComponent(color: bidTitleColor, dateTimeFormat: environment.dateTimeFormat, title: bidTitle, giftTitle: giftTitle, giftNumber: giftNumber))
|
||||
myBidComponent = AnyComponent(PeerComponent(context: component.context, theme: environment.theme, groupingSeparator: environment.dateTimeFormat.groupingSeparator, peer: peer, place: place, amount: myBidAmount, status: bidStatus, isLast: true, action: nil))
|
||||
myBidComponent = AnyComponent(PeerComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
groupingSeparator: environment.dateTimeFormat.groupingSeparator,
|
||||
peer: peer,
|
||||
place: place,
|
||||
placeIsApproximate: placeAndIsApproximate.isApproximate,
|
||||
amount: myBidAmount,
|
||||
status: bidStatus,
|
||||
isLast: true,
|
||||
action: nil
|
||||
))
|
||||
}
|
||||
|
||||
var i: Int32 = 1
|
||||
@@ -2259,7 +2293,17 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
break
|
||||
}
|
||||
}
|
||||
topBidsComponents.append((peer.id, AnyComponent(PeerComponent(context: component.context, theme: environment.theme, groupingSeparator: environment.dateTimeFormat.groupingSeparator, peer: peer, place: i, amount: bid, isLast: i == topBidders.count, action: nil))))
|
||||
topBidsComponents.append((peer.id, AnyComponent(PeerComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
groupingSeparator: environment.dateTimeFormat.groupingSeparator,
|
||||
peer: peer,
|
||||
place: i,
|
||||
placeIsApproximate: false,
|
||||
amount: bid,
|
||||
isLast: i == topBidders.count,
|
||||
action: nil
|
||||
))))
|
||||
i += 1
|
||||
}
|
||||
|
||||
@@ -2645,7 +2689,7 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: environment.theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
@@ -2678,7 +2722,7 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
content: LottieComponent.AppBundleContent(
|
||||
name: "anim_morewide"
|
||||
),
|
||||
color: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor,
|
||||
color: environment.theme.chat.inputPanel.panelControlColor,
|
||||
size: CGSize(width: 34.0, height: 34.0),
|
||||
playOnce: self.moreButtonPlayOnce
|
||||
)
|
||||
|
||||
+265
-325
@@ -8,347 +8,287 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import UrlEscaping
|
||||
import ComponentFlow
|
||||
import AlertComponent
|
||||
import StarsWithdrawalScreen
|
||||
|
||||
private final class GiftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
private let theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
private let dateTimeFormat: PresentationDateTimeFormat
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let placeholder: String
|
||||
private let minValue: Int64
|
||||
fileprivate var value: Int64
|
||||
func giftAuctionCustomBidController(
|
||||
context: AccountContext,
|
||||
title: String,
|
||||
text: String,
|
||||
placeholder: String,
|
||||
action: String,
|
||||
minValue: Int64,
|
||||
value: Int64,
|
||||
apply: @escaping (Int64) -> Void,
|
||||
cancel: @escaping () -> Void
|
||||
) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
private let backgroundView = UIImageView()
|
||||
let amountField = ComponentView<Empty>()
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
var complete: (() -> Void)? {
|
||||
didSet {
|
||||
//self.inputFieldNode.complete = self.complete
|
||||
}
|
||||
}
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, actions: [TextAlertAction], title: String, text: String, placeholder: String, minValue: Int64, value: Int64) {
|
||||
self.theme = ptheme
|
||||
self.strings = strings
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.placeholder = placeholder
|
||||
self.minValue = minValue
|
||||
self.value = value
|
||||
let inputState = AlertAmountFieldComponent.ExternalState()
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 8
|
||||
|
||||
// self.inputFieldNode = GiftAuctionCustomBidInputFieldNode(theme: ptheme, placeholder: placeholder)
|
||||
// self.inputFieldNode.text = "\(value)"
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
// self.addSubnode(self.inputFieldNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||
|
||||
let hadValidLayout = self.validLayout != nil
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
let spacing: CGFloat = 5.0
|
||||
|
||||
let titleSize = self.titleNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 4.0
|
||||
|
||||
let textSize = self.textNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 6.0 + spacing
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
|
||||
|
||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||
contentWidth = max(contentWidth, 234.0)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultWidth = contentWidth + insets.left + insets.right
|
||||
|
||||
let fieldWidth = resultWidth - 18.0
|
||||
let amountFieldSize = self.amountField.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
AmountFieldComponent(
|
||||
textColor: self.theme.list.itemPrimaryTextColor,
|
||||
secondaryColor: self.theme.list.itemSecondaryTextColor,
|
||||
placeholderColor: self.theme.list.itemPlaceholderTextColor,
|
||||
accentColor: self.theme.list.itemAccentColor,
|
||||
value: self.value,
|
||||
minValue: self.minValue,
|
||||
forceMinValue: false,
|
||||
allowZero: false,
|
||||
maxValue: nil,
|
||||
placeholderText: self.placeholder,
|
||||
textFieldOffset: CGPoint(x: -4.0, y: -1.0),
|
||||
labelText: nil,
|
||||
currency: .stars,
|
||||
dateTimeFormat: self.dateTimeFormat,
|
||||
amountUpdated: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let value {
|
||||
self.value = value
|
||||
}
|
||||
},
|
||||
tag: nil
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: fieldWidth, height: 44.0)
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: title)
|
||||
)
|
||||
var amountFieldFrame = CGRect(origin: CGPoint(x: floor((resultWidth - fieldWidth) / 2.0), y: origin.y - 2.0), size: amountFieldSize)
|
||||
if let amountFieldView = self.amountField.view {
|
||||
if amountFieldView.superview == nil {
|
||||
amountFieldView.clipsToBounds = true
|
||||
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
|
||||
self.view.addSubview(self.backgroundView)
|
||||
self.view.addSubview(amountFieldView)
|
||||
}
|
||||
self.backgroundView.frame = amountFieldFrame.insetBy(dx: 7.0, dy: 9.0)
|
||||
amountFieldFrame.size.width -= 14.0
|
||||
amountFieldView.frame = amountFieldFrame
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + amountFieldSize.height + actionsHeight + insets.top + insets.bottom + 3.0)
|
||||
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(text))
|
||||
)
|
||||
))
|
||||
|
||||
var applyImpl: (() -> Void)?
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "input",
|
||||
component: AnyComponent(
|
||||
AlertAmountFieldComponent(
|
||||
context: context,
|
||||
initialValue: value,
|
||||
minValue: minValue,
|
||||
maxValue: nil,
|
||||
placeholder: placeholder,
|
||||
isInitiallyFocused: true,
|
||||
externalState: inputState,
|
||||
returnKeyAction: {
|
||||
applyImpl?()
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
let alertController = AlertScreen(
|
||||
context: context,
|
||||
configuration: AlertScreen.Configuration(allowInputInset: true),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: strings.Common_Cancel, action: {
|
||||
cancel()
|
||||
}),
|
||||
.init(title: action, type: .default, action: {
|
||||
applyImpl?()
|
||||
}, autoDismiss: false)
|
||||
]
|
||||
)
|
||||
applyImpl = {
|
||||
if let value = inputState.value, value >= minValue {
|
||||
apply(value)
|
||||
} else {
|
||||
inputState.resetToMinValue()
|
||||
inputState.animateError()
|
||||
}
|
||||
}
|
||||
return alertController
|
||||
}
|
||||
|
||||
private final class AlertAmountFieldComponent: Component {
|
||||
public typealias EnvironmentType = AlertComponentEnvironment
|
||||
|
||||
public class ExternalState {
|
||||
public fileprivate(set) var value: Int64?
|
||||
public fileprivate(set) var animateError: () -> Void = {}
|
||||
public fileprivate(set) var activateInput: () -> Void = {}
|
||||
public fileprivate(set) var resetToMinValue: () -> Void = {}
|
||||
fileprivate let valuePromise = ValuePromise<Int64?>(nil)
|
||||
public var valueSignal: Signal<Int64?, NoError> {
|
||||
return self.valuePromise.get()
|
||||
}
|
||||
|
||||
if !hadValidLayout {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let initialValue: Int64?
|
||||
let minValue: Int64?
|
||||
let maxValue: Int64?
|
||||
let placeholder: String
|
||||
let isInitiallyFocused: Bool
|
||||
let externalState: ExternalState
|
||||
let returnKeyAction: (() -> Void)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
initialValue: Int64? = nil,
|
||||
minValue: Int64? = nil,
|
||||
maxValue: Int64? = nil,
|
||||
placeholder: String,
|
||||
isInitiallyFocused: Bool = false,
|
||||
externalState: ExternalState,
|
||||
returnKeyAction: (() -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.initialValue = initialValue
|
||||
self.minValue = minValue
|
||||
self.maxValue = maxValue
|
||||
self.placeholder = placeholder
|
||||
self.isInitiallyFocused = isInitiallyFocused
|
||||
self.externalState = externalState
|
||||
self.returnKeyAction = returnKeyAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: AlertAmountFieldComponent, rhs: AlertAmountFieldComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.initialValue != rhs.initialValue {
|
||||
return false
|
||||
}
|
||||
if lhs.minValue != rhs.minValue {
|
||||
return false
|
||||
}
|
||||
if lhs.maxValue != rhs.maxValue {
|
||||
return false
|
||||
}
|
||||
if lhs.placeholder != rhs.placeholder {
|
||||
return false
|
||||
}
|
||||
if lhs.isInitiallyFocused != rhs.isInitiallyFocused {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView, UITextFieldDelegate {
|
||||
private let background = ComponentView<Empty>()
|
||||
private let amountField = ComponentView<Empty>()
|
||||
|
||||
private var currentValue: Int64?
|
||||
|
||||
private var component: AlertAmountFieldComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var isUpdating = false
|
||||
|
||||
func activateInput() {
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
amountFieldView.activateInput()
|
||||
Queue.mainQueue().justDispatch {
|
||||
amountFieldView.selectAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
self.value = self.minValue
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
amountFieldView.resetValue()
|
||||
func resetToMinValue() {
|
||||
self.currentValue = self.component?.minValue
|
||||
self.state?.updated()
|
||||
|
||||
amountFieldView.animateError()
|
||||
amountFieldView.selectAll()
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
amountFieldView.resetValue()
|
||||
amountFieldView.selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
amountFieldView.animateError()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: AlertAmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
if self.component == nil {
|
||||
self.currentValue = component.initialValue
|
||||
|
||||
component.externalState.animateError = { [weak self] in
|
||||
self?.animateError()
|
||||
}
|
||||
component.externalState.activateInput = { [weak self] in
|
||||
self?.activateInput()
|
||||
}
|
||||
component.externalState.resetToMinValue = { [weak self] in
|
||||
self?.resetToMinValue()
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstTime = self.component == nil
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let environment = environment[AlertComponentEnvironment.self]
|
||||
|
||||
let topInset: CGFloat = 15.0
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let amountFieldSize = self.amountField.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
AmountFieldComponent(
|
||||
textColor: environment.theme.actionSheet.primaryTextColor,
|
||||
secondaryColor: environment.theme.actionSheet.secondaryTextColor,
|
||||
placeholderColor: environment.theme.actionSheet.inputPlaceholderColor,
|
||||
accentColor: environment.theme.actionSheet.controlAccentColor,
|
||||
value: self.currentValue,
|
||||
minValue: component.minValue,
|
||||
forceMinValue: false,
|
||||
allowZero: false,
|
||||
maxValue: nil,
|
||||
placeholderText: component.placeholder,
|
||||
textFieldOffset: CGPoint(x: -4.0, y: -1.0),
|
||||
labelText: nil,
|
||||
currency: .stars,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
amountUpdated: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.currentValue = value
|
||||
component.externalState.value = value
|
||||
component.externalState.valuePromise.set(value)
|
||||
},
|
||||
tag: nil
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 44.0)
|
||||
)
|
||||
var amountFieldFrame = CGRect(origin: CGPoint(x: -16.0, y: topInset - 1.0 + UIScreenPixel), size: amountFieldSize)
|
||||
if let amountFieldView = self.amountField.view {
|
||||
if amountFieldView.superview == nil {
|
||||
amountFieldView.clipsToBounds = true
|
||||
self.addSubview(amountFieldView)
|
||||
}
|
||||
amountFieldFrame.size.width -= 14.0
|
||||
amountFieldView.frame = amountFieldFrame
|
||||
}
|
||||
|
||||
let backgroundPadding: CGFloat = 14.0
|
||||
let size = CGSize(width: availableSize.width, height: 50.0)
|
||||
|
||||
let backgroundSize = self.background.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .value(25.0), smoothCorners: false)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width + backgroundPadding * 2.0, height: size.height)
|
||||
)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: topInset ), size: backgroundSize)
|
||||
if let backgroundView = self.background.view {
|
||||
if backgroundView.superview == nil {
|
||||
self.addSubview(backgroundView)
|
||||
}
|
||||
transition.setFrame(view: backgroundView, frame: backgroundFrame)
|
||||
}
|
||||
|
||||
if isFirstTime && component.isInitiallyFocused {
|
||||
self.activateInput()
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: size.height + topInset)
|
||||
}
|
||||
}
|
||||
|
||||
func deactivateInput() {
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
amountFieldView.deactivateInput()
|
||||
}
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func giftAuctionCustomBidController(context: AccountContext, title: String, text: String, placeholder: String, action: String, minValue: Int64, value: Int64, apply: @escaping (Int64) -> Void, cancel: @escaping () -> Void) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var applyImpl: (() -> Void)?
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
cancel()
|
||||
}), TextAlertAction(type: .defaultAction, title: action, action: {
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = GiftAuctionCustomBidAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, actions: actions, title: title, text: text, placeholder: placeholder, minValue: minValue, value: value)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
applyImpl = { [weak contentNode] in
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
let value = contentNode.value
|
||||
if value < minValue {
|
||||
contentNode.animateError()
|
||||
} else {
|
||||
dismissImpl?(true)
|
||||
apply(contentNode.value)
|
||||
}
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
})
|
||||
controller.dismissed = { byTapOutside in
|
||||
presentationDataDisposable.dispose()
|
||||
if byTapOutside {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
dismissImpl = { [weak controller, weak contentNode] animated in
|
||||
contentNode?.deactivateInput()
|
||||
let _ = contentNode
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
+2
-3
@@ -256,7 +256,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak state] _ in
|
||||
@@ -296,8 +296,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
|
||||
+18
-5
@@ -28,23 +28,27 @@ import ButtonComponent
|
||||
import UndoUI
|
||||
import LottieComponent
|
||||
import AnimatedTextComponent
|
||||
import TableComponent
|
||||
|
||||
private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let auctionContext: GiftAuctionContext
|
||||
let peerId: EnginePeer.Id?
|
||||
let animateOut: ActionSlot<Action<()>>
|
||||
let getController: () -> ViewController?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
auctionContext: GiftAuctionContext,
|
||||
peerId: EnginePeer.Id?,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.auctionContext = auctionContext
|
||||
self.peerId = peerId
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
}
|
||||
@@ -61,6 +65,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
|
||||
private let context: AccountContext
|
||||
private let auctionContext: GiftAuctionContext
|
||||
private let peerId: EnginePeer.Id?
|
||||
private let animateOut: ActionSlot<Action<()>>
|
||||
private let getController: () -> ViewController?
|
||||
|
||||
@@ -92,11 +97,13 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
init(
|
||||
context: AccountContext,
|
||||
auctionContext: GiftAuctionContext,
|
||||
peerId: EnginePeer.Id?,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.auctionContext = auctionContext
|
||||
self.peerId = peerId
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
|
||||
@@ -398,7 +405,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
}
|
||||
let storeController = self.context.sharedContext.makeGiftStoreController(
|
||||
context: self.context,
|
||||
peerId: self.context.account.peerId,
|
||||
peerId: self.peerId ?? self.context.account.peerId,
|
||||
gift: gift
|
||||
)
|
||||
controller.push(storeController)
|
||||
@@ -472,7 +479,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, auctionContext: self.auctionContext, animateOut: self.animateOut, getController: self.getController)
|
||||
return State(context: self.context, auctionContext: self.auctionContext, peerId: self.peerId, animateOut: self.animateOut, getController: self.getController)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
@@ -1075,7 +1082,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
guard let state, let attributes = state.giftUpgradeAttributes else {
|
||||
return
|
||||
}
|
||||
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsPreviewScreen(context: component.context, gift: .generic(gift), attributes: attributes)
|
||||
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), attributes: attributes, selectedAttributes: nil, focusedAttribute: nil)
|
||||
environment.controller()?.push(variantsController)
|
||||
}, animateScale: false),
|
||||
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),
|
||||
@@ -1276,13 +1283,16 @@ final class GiftAuctionViewSheetComponent: CombinedComponent {
|
||||
|
||||
let context: AccountContext
|
||||
let auctionContext: GiftAuctionContext
|
||||
let peerId: EnginePeer.Id?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
auctionContext: GiftAuctionContext
|
||||
auctionContext: GiftAuctionContext,
|
||||
peerId: EnginePeer.Id?
|
||||
) {
|
||||
self.context = context
|
||||
self.auctionContext = auctionContext
|
||||
self.peerId = peerId
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftAuctionViewSheetComponent, rhs: GiftAuctionViewSheetComponent) -> Bool {
|
||||
@@ -1307,6 +1317,7 @@ final class GiftAuctionViewSheetComponent: CombinedComponent {
|
||||
content: AnyComponent<EnvironmentType>(GiftAuctionViewSheetContent(
|
||||
context: context.component.context,
|
||||
auctionContext: context.component.auctionContext,
|
||||
peerId: context.component.peerId,
|
||||
animateOut: animateOut,
|
||||
getController: controller
|
||||
)),
|
||||
@@ -1391,6 +1402,7 @@ public final class GiftAuctionViewScreen: ViewControllerComponentContainer {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
auctionContext: GiftAuctionContext,
|
||||
peerId: EnginePeer.Id?,
|
||||
completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void
|
||||
) {
|
||||
self.completion = completion
|
||||
@@ -1399,7 +1411,8 @@ public final class GiftAuctionViewScreen: ViewControllerComponentContainer {
|
||||
context: context,
|
||||
component: GiftAuctionViewSheetComponent(
|
||||
context: context,
|
||||
auctionContext: auctionContext
|
||||
auctionContext: auctionContext,
|
||||
peerId: peerId
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import GiftAnimationComponent
|
||||
import GlassBarButtonComponent
|
||||
import GiftRemainingCountComponent
|
||||
import AnimatedTextComponent
|
||||
import AvatarComponent
|
||||
|
||||
private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@@ -199,7 +200,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let component = context.component
|
||||
let theme = environment.theme
|
||||
let theme = environment.theme.withModalBlocksBackground()
|
||||
let strings = environment.strings
|
||||
let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
|
||||
let controller = environment.controller
|
||||
|
||||
+207
-475
@@ -3,478 +3,32 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
import GiftItemComponent
|
||||
import ChatMessagePaymentAlertController
|
||||
import ActivityIndicator
|
||||
import TooltipUI
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import BundleIconComponent
|
||||
import TelegramStringFormatting
|
||||
|
||||
private final class GiftOfferAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private var presentationTheme: PresentationTheme
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let amount: CurrencyAmount
|
||||
private let gift: StarGift.UniqueGift
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let giftView = ComponentView<Empty>()
|
||||
private let textNode: ASTextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let tableView = ComponentView<Empty>()
|
||||
private let valueDelta = ComponentView<Empty>()
|
||||
|
||||
private let modelButtonTag = GenericComponentViewTag()
|
||||
private let backdropButtonTag = GenericComponentViewTag()
|
||||
private let symbolButtonTag = GenericComponentViewTag()
|
||||
|
||||
fileprivate var getController: () -> ViewController? = { return nil}
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
var inProgress = false {
|
||||
didSet {
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
ptheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
gift: StarGift.UniqueGift,
|
||||
peer: EnginePeer,
|
||||
title: String,
|
||||
text: String,
|
||||
amount: CurrencyAmount,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.presentationTheme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.amount = amount
|
||||
self.gift = gift
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9))
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func dismissAllTooltips() {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
controller.window?.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
})
|
||||
controller.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func showAttributeInfo(tag: Any, text: String) {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
self.dismissAllTooltips()
|
||||
|
||||
guard let sourceView = self.tableView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
controller.present(tooltipController, in: .current)
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 310.0)
|
||||
|
||||
let strings = self.strings
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
|
||||
|
||||
let _ = self.giftView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationTheme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: avatarSize
|
||||
)
|
||||
if let view = self.giftView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = giftFrame
|
||||
}
|
||||
|
||||
if let arrowImage = self.arrowNode.image {
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
|
||||
origin.y += avatarSize.height + 17.0
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 5.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor
|
||||
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
|
||||
.model, .pattern, .backdrop, .originalInfo
|
||||
]
|
||||
|
||||
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
|
||||
for attribute in self.gift.attributes {
|
||||
attributeMap[attribute.attributeType] = attribute
|
||||
}
|
||||
|
||||
for type in order {
|
||||
if let attribute = attributeMap[type] {
|
||||
let id: String?
|
||||
let title: String?
|
||||
let value: NSAttributedString
|
||||
let percentage: Float?
|
||||
let tag: AnyObject?
|
||||
|
||||
switch attribute {
|
||||
case let .model(name, _, rarity):
|
||||
id = "model"
|
||||
title = strings.Gift_Unique_Model
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = self.modelButtonTag
|
||||
case let .backdrop(name, _, _, _, _, _, rarity):
|
||||
id = "backdrop"
|
||||
title = strings.Gift_Unique_Backdrop
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = self.backdropButtonTag
|
||||
case let .pattern(name, _, rarity):
|
||||
id = "pattern"
|
||||
title = strings.Gift_Unique_Symbol
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = self.symbolButtonTag
|
||||
case .originalInfo:
|
||||
continue
|
||||
}
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(value))
|
||||
)
|
||||
)
|
||||
)
|
||||
if let percentage, let tag {
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: self.context,
|
||||
text: formatPercentage(percentage),
|
||||
color: self.presentationTheme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
}
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
|
||||
tableItems.append(.init(
|
||||
id: id,
|
||||
title: title,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let tableSize = self.tableView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
TableComponent(
|
||||
theme: self.presentationTheme,
|
||||
items: tableItems,
|
||||
semiTransparent: true
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
|
||||
)
|
||||
let tableFrame = CGRect(origin: CGPoint(x: 16.0, y: avatarSize.height + titleSize.height + textSize.height + 60.0), size: tableSize)
|
||||
if let view = self.tableView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = tableFrame
|
||||
}
|
||||
|
||||
var valueDeltaHeight: CGFloat = 0.0
|
||||
if let valueAmount = self.gift.valueUsdAmount {
|
||||
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let usdRate: Double
|
||||
switch self.amount.currency {
|
||||
case .stars:
|
||||
usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0
|
||||
case .ton:
|
||||
usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0
|
||||
}
|
||||
let offerUsdValue = Double(self.amount.amount.value) * usdRate
|
||||
let giftUsdValue = Double(valueAmount) / 100.0
|
||||
|
||||
let fraction = giftUsdValue / offerUsdValue
|
||||
let percentage = Int(fraction * 100) - 100
|
||||
|
||||
if percentage > 20 {
|
||||
let textColor = self.presentationTheme.list.itemDestructiveColor
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
)
|
||||
let valueDeltaSize = self.valueDelta.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
BalancedTextComponent(
|
||||
text: .markdown(text: strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
|
||||
)
|
||||
let valueDeltaFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - valueDeltaSize.width) / 2.0), y: avatarSize.height + titleSize.height + textSize.height + 73.0 + tableSize.height), size: valueDeltaSize)
|
||||
if let view = self.valueDelta.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = valueDeltaFrame
|
||||
}
|
||||
valueDeltaHeight += valueDeltaSize.height + 10.0
|
||||
}
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + valueDeltaHeight + 40.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
do {
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
do {
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
do {
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if self.inProgress {
|
||||
let activityIndicator: ActivityIndicator
|
||||
if let current = self.activityIndicator {
|
||||
activityIndicator = current
|
||||
} else {
|
||||
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
|
||||
self.addSubnode(activityIndicator)
|
||||
}
|
||||
|
||||
if let actionNode = self.actionNodes.first {
|
||||
actionNode.isUserInteractionEnabled = false
|
||||
actionNode.isHidden = false
|
||||
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
import AlertComponent
|
||||
import TableComponent
|
||||
import AvatarComponent
|
||||
import AlertTransferHeaderComponent
|
||||
import AlertTableComponent
|
||||
|
||||
public func giftOfferAlertController(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
gift: StarGift.UniqueGift,
|
||||
peer: EnginePeer,
|
||||
amount: CurrencyAmount,
|
||||
commit: @escaping () -> Void
|
||||
) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
) -> ViewController {
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
let title = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Title
|
||||
@@ -485,7 +39,7 @@ public func giftOfferAlertController(
|
||||
case .stars:
|
||||
priceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(Int32(clamping: amount.amount.value))
|
||||
case .ton:
|
||||
priceString = "\(amount.amount) TON"
|
||||
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: presentationData.dateTimeFormat) + " TON"
|
||||
}
|
||||
|
||||
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
@@ -502,27 +56,205 @@ public func giftOfferAlertController(
|
||||
let giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
let text = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text(giftTitle, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), priceString, finalPriceString).string
|
||||
|
||||
var contentNode: GiftOfferAlertContentNode?
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in
|
||||
contentNode?.inProgress = true
|
||||
commit()
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
})]
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = presentationData.theme.list.itemPrimaryTextColor
|
||||
|
||||
contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, amount: amount, actions: actions)
|
||||
let modelButtonTag = GenericComponentViewTag()
|
||||
let backdropButtonTag = GenericComponentViewTag()
|
||||
let symbolButtonTag = GenericComponentViewTag()
|
||||
var showAttributeInfoImpl: ((Any, String) -> Void)?
|
||||
|
||||
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: nil, chatPeerId: context.account.peerId, showBalance: false)
|
||||
contentNode?.getController = { [weak controller] in
|
||||
return controller
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
|
||||
.model, .pattern, .backdrop, .originalInfo
|
||||
]
|
||||
|
||||
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
|
||||
for attribute in gift.attributes {
|
||||
attributeMap[attribute.attributeType] = attribute
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
|
||||
for type in order {
|
||||
if let attribute = attributeMap[type] {
|
||||
let id: String?
|
||||
let title: String?
|
||||
let value: NSAttributedString
|
||||
let percentage: Float?
|
||||
let tag: AnyObject?
|
||||
|
||||
switch attribute {
|
||||
case let .model(name, _, rarity):
|
||||
id = "model"
|
||||
title = strings.Gift_Unique_Model
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = modelButtonTag
|
||||
case let .backdrop(name, _, _, _, _, _, rarity):
|
||||
id = "backdrop"
|
||||
title = strings.Gift_Unique_Backdrop
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = backdropButtonTag
|
||||
case let .pattern(name, _, rarity):
|
||||
id = "pattern"
|
||||
title = strings.Gift_Unique_Symbol
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = symbolButtonTag
|
||||
case .originalInfo:
|
||||
continue
|
||||
}
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(value))
|
||||
)
|
||||
)
|
||||
)
|
||||
if let percentage, let tag {
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: context,
|
||||
text: formatPercentage(percentage),
|
||||
color: presentationData.theme.list.itemAccentColor
|
||||
)),
|
||||
action: {
|
||||
showAttributeInfoImpl?(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
}
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
|
||||
tableItems.append(.init(
|
||||
id: id,
|
||||
title: title,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
}
|
||||
return controller
|
||||
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "header",
|
||||
component: AnyComponent(
|
||||
AlertTransferHeaderComponent(
|
||||
fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
)),
|
||||
toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
peer: peer
|
||||
)
|
||||
)),
|
||||
type: .transfer
|
||||
)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: title)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(text))
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "table",
|
||||
component: AnyComponent(
|
||||
AlertTableComponent(items: tableItems)
|
||||
)
|
||||
))
|
||||
|
||||
if let valueAmount = gift.valueUsdAmount {
|
||||
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let usdRate: Double
|
||||
switch amount.currency {
|
||||
case .stars:
|
||||
usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0
|
||||
case .ton:
|
||||
usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0
|
||||
}
|
||||
let offerUsdValue = Double(amount.amount.value) * usdRate
|
||||
let giftUsdValue = Double(valueAmount) / 100.0
|
||||
|
||||
let fraction = giftUsdValue / offerUsdValue
|
||||
let percentage = Int(fraction * 100) - 100
|
||||
|
||||
if percentage > 20 {
|
||||
let warningText = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "warning",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(warningText), color: .destructive, style: .plain(.small))
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let updatedPresentationDataSignal = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let alertController = AlertScreen(
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: buttonText, type: .default, action: {
|
||||
commit()
|
||||
}),
|
||||
.init(title: strings.Common_Cancel)
|
||||
],
|
||||
updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal)
|
||||
)
|
||||
|
||||
var dismissAllTooltipsImpl: (() -> Void)?
|
||||
showAttributeInfoImpl = { [weak alertController] tag, text in
|
||||
dismissAllTooltipsImpl?()
|
||||
guard let alertController, let sourceView = alertController.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: alertController.view) else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
alertController.present(tooltipController, in: .current)
|
||||
}
|
||||
dismissAllTooltipsImpl = { [weak alertController] in
|
||||
guard let alertController else {
|
||||
return
|
||||
}
|
||||
alertController.window?.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
})
|
||||
alertController.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return alertController
|
||||
}
|
||||
|
||||
+235
-471
@@ -11,440 +11,16 @@ import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
import GiftItemComponent
|
||||
import ChatMessagePaymentAlertController
|
||||
import ActivityIndicator
|
||||
import TabSelectorComponent
|
||||
import BundleIconComponent
|
||||
import MultilineTextComponent
|
||||
import TelegramStringFormatting
|
||||
import TooltipUI
|
||||
|
||||
private final class GiftPurchaseAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private var presentationTheme: PresentationTheme
|
||||
private let gift: StarGift.UniqueGift
|
||||
private let peer: EnginePeer
|
||||
|
||||
fileprivate var currency: CurrencyAmount.Currency
|
||||
|
||||
fileprivate let header = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private let giftView = ComponentView<Empty>()
|
||||
private let arrow = ComponentView<Empty>()
|
||||
private let avatarNode: AvatarNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
var inProgress = false {
|
||||
didSet {
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatedCurrency: (CurrencyAmount.Currency) -> Void = { _ in }
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
presentationTheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
gift: StarGift.UniqueGift,
|
||||
peer: EnginePeer,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.presentationTheme = presentationTheme
|
||||
self.gift = gift
|
||||
self.peer = peer
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
self.currency = self.gift.resellForTonOnly ? .ton : .stars
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.avatarNode.setPeer(context: context, theme: presentationTheme, peer: peer)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func requestUpdate(transition: ContainedViewLayoutTransition) {
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let containerSize = size
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
|
||||
var origin = CGPoint(x: 0.0, y: 20.0)
|
||||
if self.gift.resellForTonOnly {
|
||||
let headerSize = self.header.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: self.strings.Gift_Buy_AcceptsTonOnly, font: Font.regular(13.0), textColor: self.presentationTheme.actionSheet.secondaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 2
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width - 32.0, height: size.height)
|
||||
)
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - headerSize.width) / 2.0), y: origin.y), size: headerSize)
|
||||
if let view = self.header.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = headerFrame
|
||||
}
|
||||
origin.y += headerSize.height + 17.0
|
||||
} else {
|
||||
origin.y -= 4.0
|
||||
|
||||
let headerSize = self.header.update(
|
||||
transition: ComponentTransition(transition),
|
||||
component: AnyComponent(TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
foreground: self.presentationTheme.list.itemSecondaryTextColor,
|
||||
selection: self.presentationTheme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15),
|
||||
simple: true
|
||||
),
|
||||
theme: self.presentationTheme,
|
||||
customLayout: TabSelectorComponent.CustomLayout(
|
||||
font: Font.medium(14.0),
|
||||
spacing: 10.0
|
||||
),
|
||||
items: [
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
content: .text(self.strings.Gift_Buy_PayInStars)
|
||||
),
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
content: .text(self.strings.Gift_Buy_PayInTon)
|
||||
)
|
||||
],
|
||||
selectedId: self.currency == .ton ? AnyHashable(1) : AnyHashable(0),
|
||||
setSelectedId: { [weak self] id in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let currency: CurrencyAmount.Currency
|
||||
if id == AnyHashable(0) {
|
||||
currency = .stars
|
||||
} else {
|
||||
currency = .ton
|
||||
}
|
||||
if self.currency != currency {
|
||||
self.currency = currency
|
||||
self.updatedCurrency(currency)
|
||||
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerSize.width - 16.0 * 2.0, height: 100.0)
|
||||
)
|
||||
|
||||
size.width = min(containerSize.width, max(270.0, headerSize.width + 32.0))
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - headerSize.width) / 2.0), y: origin.y), size: headerSize)
|
||||
if let view = self.header.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = headerFrame
|
||||
}
|
||||
origin.y += headerSize.height + 17.0
|
||||
}
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var resellPrice: CurrencyAmount?
|
||||
if let actionNode = self.actionNodes.first {
|
||||
switch self.currency {
|
||||
case .stars:
|
||||
if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .stars }) {
|
||||
resellPrice = resellAmount
|
||||
actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyFor(Int32(resellAmount.amount.value)), action: actionNode.action.action)
|
||||
}
|
||||
case .ton:
|
||||
if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .ton }) {
|
||||
resellPrice = resellAmount
|
||||
let valueString = formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyForTon(valueString).string, action: actionNode.action.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
|
||||
|
||||
let _ = self.giftView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationTheme,
|
||||
strings: self.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: avatarSize
|
||||
)
|
||||
if let view = self.giftView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = giftFrame
|
||||
}
|
||||
|
||||
let arrowSize = self.arrow.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Peer Info/AlertArrow", tintColor: self.presentationTheme.actionSheet.secondaryTextColor)),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowSize.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowSize.height) / 2.0)), size: arrowSize)
|
||||
if let view = self.arrow.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = arrowFrame
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
origin.y += avatarSize.height + 17.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: self.strings.Gift_Buy_Confirm_Title, font: Font.semibold(17.0), textColor: self.presentationTheme.actionSheet.primaryTextColor)),
|
||||
horizontalAlignment: .center
|
||||
)
|
||||
),
|
||||
environment: {
|
||||
},
|
||||
containerSize: CGSize(width: size.width - 32.0, height: size.height)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = titleFrame
|
||||
}
|
||||
origin.y += titleSize.height + 5.0
|
||||
|
||||
let giftTitle = "\(self.gift.title) #\(presentationStringsFormattedNumber(self.gift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
|
||||
let priceString: String
|
||||
if let resellPrice {
|
||||
switch resellPrice.currency {
|
||||
case .stars:
|
||||
priceString = self.strings.Gift_Buy_Confirm_Text_Stars(Int32(clamping: resellPrice.amount.value))
|
||||
case .ton:
|
||||
priceString = "**\(formatTonAmountText(resellPrice.amount.value, dateTimeFormat: presentationData.dateTimeFormat)) TON**"
|
||||
}
|
||||
} else {
|
||||
priceString = ""
|
||||
}
|
||||
|
||||
let text: String
|
||||
if self.peer.id == self.context.account.peerId {
|
||||
text = self.strings.Gift_Buy_Confirm_Text(giftTitle, priceString).string
|
||||
} else {
|
||||
text = self.strings.Gift_Buy_Confirm_GiftText(giftTitle, priceString, self.peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .markdown(text: text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationTheme.actionSheet.primaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: self.presentationTheme.actionSheet.primaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationTheme.actionSheet.primaryTextColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
)
|
||||
),
|
||||
environment: {
|
||||
},
|
||||
containerSize: CGSize(width: size.width - 32.0, height: size.height)
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)
|
||||
if let view = self.text.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = textFrame
|
||||
}
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: origin.y + actionsHeight - 26.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
//let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
/*switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:*/
|
||||
do {
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
/*switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:*/
|
||||
do {
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
/*switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:*/
|
||||
do {
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if self.inProgress {
|
||||
let activityIndicator: ActivityIndicator
|
||||
if let current = self.activityIndicator {
|
||||
activityIndicator = current
|
||||
} else {
|
||||
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
|
||||
self.addSubnode(activityIndicator)
|
||||
}
|
||||
|
||||
if let actionNode = self.actionNodes.first {
|
||||
actionNode.isUserInteractionEnabled = false
|
||||
actionNode.isHidden = false
|
||||
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
import AlertComponent
|
||||
import AlertTransferHeaderComponent
|
||||
import AvatarComponent
|
||||
|
||||
public func giftPurchaseAlertController(
|
||||
context: AccountContext,
|
||||
@@ -454,68 +30,256 @@ public func giftPurchaseAlertController(
|
||||
navigationController: NavigationController?,
|
||||
commit: @escaping (CurrencyAmount.Currency) -> Void,
|
||||
dismissed: @escaping () -> Void
|
||||
) -> AlertController {
|
||||
) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
let currencyPromise = ValuePromise<CurrencyAmount.Currency>(.stars)
|
||||
if gift.resellForTonOnly {
|
||||
currencyPromise.set(.ton)
|
||||
}
|
||||
|
||||
let contentSignal = currencyPromise.get()
|
||||
|> map { currency in
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
if gift.resellForTonOnly {
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "tonOnly",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(
|
||||
content: .plain(strings.Gift_Buy_AcceptsTonOnly),
|
||||
alignment: .center,
|
||||
color: .secondary,
|
||||
style: .plain(.small),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 8.0, right: 0.0)
|
||||
)
|
||||
)
|
||||
))
|
||||
} else {
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "currency",
|
||||
component: AnyComponent(
|
||||
AlertCurrencyComponent(
|
||||
currency: currency,
|
||||
updatedCurrency: { currency in
|
||||
currencyPromise.set(currency)
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "header",
|
||||
component: AnyComponent(
|
||||
AlertTransferHeaderComponent(
|
||||
fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
)),
|
||||
toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
peer: peer
|
||||
)
|
||||
)),
|
||||
type: .transfer
|
||||
)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: strings.Gift_Buy_Confirm_Title)
|
||||
)
|
||||
))
|
||||
|
||||
var contentNode: GiftPurchaseAlertContentNode?
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var commitImpl: (() -> Void)?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: "", action: {
|
||||
commitImpl?()
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
})]
|
||||
let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
var priceString = ""
|
||||
switch currency {
|
||||
case .stars:
|
||||
if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .stars }) {
|
||||
priceString = strings.Gift_Buy_Confirm_Text_Stars(Int32(clamping: resellAmount.amount.value))
|
||||
}
|
||||
case .ton:
|
||||
if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .ton }) {
|
||||
priceString = "**\(formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)) TON**"
|
||||
}
|
||||
}
|
||||
|
||||
contentNode = GiftPurchaseAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationTheme: presentationData.theme, strings: strings, gift: gift, peer: peer, actions: actions)
|
||||
let text: String
|
||||
if peer.id == context.account.peerId {
|
||||
text = strings.Gift_Buy_Confirm_Text(giftTitle, priceString).string
|
||||
} else {
|
||||
text = strings.Gift_Buy_Confirm_GiftText(giftTitle, priceString, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(text))
|
||||
)
|
||||
))
|
||||
return content
|
||||
}
|
||||
|
||||
let controller = ChatMessagePaymentAlertController(
|
||||
let actionsSignal = currencyPromise.get()
|
||||
|> map { currency in
|
||||
var actions: [AlertScreen.Action] = []
|
||||
var buyString = ""
|
||||
switch currency {
|
||||
case .stars:
|
||||
if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .stars }) {
|
||||
buyString = strings.Gift_Buy_Confirm_BuyFor(Int32(resellAmount.amount.value))
|
||||
}
|
||||
case .ton:
|
||||
if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .ton }) {
|
||||
buyString = strings.Gift_Buy_Confirm_BuyForTon(formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)).string
|
||||
}
|
||||
}
|
||||
actions.append(.init(id: "buy", title: buyString, type: .default, action: {
|
||||
commit(currency)
|
||||
}))
|
||||
actions.append(.init(title: strings.Common_Cancel))
|
||||
return actions
|
||||
}
|
||||
|
||||
let alertController = ChatMessagePaymentAlertController(
|
||||
context: context,
|
||||
presentationData: presentationData,
|
||||
contentNode: contentNode!,
|
||||
updatedPresentationData: (presentationData, context.sharedContext.presentationData),
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
|
||||
contentSignal: contentSignal,
|
||||
actionsSignal: actionsSignal,
|
||||
navigationController: navigationController,
|
||||
chatPeerId: context.account.peerId,
|
||||
showBalance: true,
|
||||
currency: gift.resellForTonOnly ? .ton : .stars,
|
||||
currencySignal: currencyPromise.get(),
|
||||
animateBalanceOverlay: animateBalanceOverlay
|
||||
)
|
||||
controller.dismissed = { _ in
|
||||
alertController.dismissed = { _ in
|
||||
dismissed()
|
||||
}
|
||||
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
commitImpl = { [weak contentNode] in
|
||||
contentNode?.inProgress = true
|
||||
commit(contentNode?.currency ?? .stars)
|
||||
}
|
||||
|
||||
// if !gift.resellForTonOnly {
|
||||
// Queue.mainQueue().after(0.3) {
|
||||
// if let headerView = contentNode?.header.view as? TabSelectorComponent.View {
|
||||
// let absoluteFrame = headerView.convert(headerView.bounds, to: nil)
|
||||
// var originX = absoluteFrame.width * 0.75
|
||||
// if let itemFrame = headerView.frameForItem(AnyHashable(1)) {
|
||||
// originX = itemFrame.midX
|
||||
// }
|
||||
// let location = CGRect(origin: CGPoint(x: absoluteFrame.minX + floor(originX), y: absoluteFrame.minY - 8.0), size: CGSize())
|
||||
// let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: presentationData.strings.Gift_Buy_PayInTon_Tooltip), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
// return .dismiss(consume: false)
|
||||
// })
|
||||
// controller.present(tooltipController, in: .window(.root))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
contentNode?.updatedCurrency = { [weak controller] currency in
|
||||
controller?.currency = currency
|
||||
return alertController
|
||||
}
|
||||
|
||||
private final class AlertCurrencyComponent: Component {
|
||||
public typealias EnvironmentType = AlertComponentEnvironment
|
||||
|
||||
let currency: CurrencyAmount.Currency
|
||||
let updatedCurrency: (CurrencyAmount.Currency) -> Void
|
||||
|
||||
public init(
|
||||
currency: CurrencyAmount.Currency,
|
||||
updatedCurrency: @escaping (CurrencyAmount.Currency) -> Void
|
||||
) {
|
||||
self.currency = currency
|
||||
self.updatedCurrency = updatedCurrency
|
||||
}
|
||||
|
||||
public static func ==(lhs: AlertCurrencyComponent, rhs: AlertCurrencyComponent) -> Bool {
|
||||
if lhs.currency != rhs.currency {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !gift.resellForTonOnly {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
if let headerView = contentNode?.header.view as? TabSelectorComponent.View {
|
||||
let absoluteFrame = headerView.convert(headerView.bounds, to: nil)
|
||||
var originX = absoluteFrame.width * 0.75
|
||||
if let itemFrame = headerView.frameForItem(AnyHashable(1)) {
|
||||
originX = itemFrame.midX
|
||||
|
||||
final class View: UIView {
|
||||
private let header = ComponentView<Empty>()
|
||||
|
||||
private var component: AlertCurrencyComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
func update(component: AlertCurrencyComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let environment = environment[AlertComponentEnvironment.self]
|
||||
|
||||
let headerSize = self.header.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
foreground: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.35),
|
||||
selection: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1),
|
||||
simple: true
|
||||
),
|
||||
theme: environment.theme,
|
||||
customLayout: TabSelectorComponent.CustomLayout(
|
||||
font: Font.medium(14.0),
|
||||
spacing: 10.0
|
||||
),
|
||||
items: [
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
content: .text(environment.strings.Gift_Buy_PayInStars)
|
||||
),
|
||||
TabSelectorComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
content: .text(environment.strings.Gift_Buy_PayInTon)
|
||||
)
|
||||
],
|
||||
selectedId: component.currency == .ton ? AnyHashable(1) : AnyHashable(0),
|
||||
setSelectedId: { [weak self] id in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let currency: CurrencyAmount.Currency
|
||||
if id == AnyHashable(0) {
|
||||
currency = .stars
|
||||
} else {
|
||||
currency = .ton
|
||||
}
|
||||
if currency != component.currency {
|
||||
component.updatedCurrency(currency)
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width + 54.0, height: 100.0)
|
||||
)
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - headerSize.width) / 2.0), y: 0.0), size: headerSize)
|
||||
if let view = self.header.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.minX + floor(originX), y: absoluteFrame.minY - 8.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: presentationData.strings.Gift_Buy_PayInTon_Tooltip), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
controller.present(tooltipController, in: .window(.root))
|
||||
view.frame = headerFrame
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: headerSize.height + 12.0)
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
+80
-311
@@ -9,298 +9,10 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import Markdown
|
||||
import ChatMessagePaymentAlertController
|
||||
import ActivityIndicator
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import TelegramStringFormatting
|
||||
import TextFormat
|
||||
|
||||
private final class GiftRemoveInfoAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private var presentationTheme: PresentationTheme
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let gift: StarGift.UniqueGift
|
||||
private let peers: [EnginePeer.Id: EnginePeer]
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
private let infoBackgroundNode: ASDisplayNode
|
||||
private let infoView = ComponentView<Empty>()
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
var inProgress = false {
|
||||
didSet {
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
ptheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
gift: StarGift.UniqueGift,
|
||||
peers: [EnginePeer.Id: EnginePeer],
|
||||
title: String,
|
||||
text: String,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.presentationTheme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.gift = gift
|
||||
self.peers = peers
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.infoBackgroundNode = ASDisplayNode()
|
||||
self.infoBackgroundNode.backgroundColor = ptheme.overallDarkAppearance ? ptheme.list.itemModalBlocksBackgroundColor : ptheme.list.itemPrimaryTextColor.withAlphaComponent(0.04)
|
||||
self.infoBackgroundNode.cornerRadius = 10.0
|
||||
self.infoBackgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.infoBackgroundNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 310.0)
|
||||
|
||||
let strings = self.strings
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 5.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
|
||||
var infoSize: CGSize = .zero
|
||||
for attribute in self.gift.attributes {
|
||||
if case let .originalInfo(senderPeerId, recipientPeerId, date, text, entities) = attribute {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let tableFont = Font.regular(13.0)
|
||||
let tableBoldFont = Font.semibold(13.0)
|
||||
let tableItalicFont = Font.italic(13.0)
|
||||
let tableBoldItalicFont = Font.semiboldItalic(13.0)
|
||||
let tableMonospaceFont = Font.monospace(13.0)
|
||||
|
||||
let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor
|
||||
let tableLinkColor = self.presentationTheme.list.itemAccentColor
|
||||
|
||||
let senderName = senderPeerId.flatMap { self.peers[$0]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) }
|
||||
let recipientName = self.peers[recipientPeerId]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? ""
|
||||
|
||||
let dateString = stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: presentationData.dateTimeFormat, withTime: false)
|
||||
let value: NSAttributedString
|
||||
if let text {
|
||||
let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: tableTextColor, linkColor: tableLinkColor, baseFont: tableFont, linkFont: tableFont, boldFont: tableBoldFont, italicFont: tableItalicFont, boldItalicFont: tableBoldItalicFont, fixedFont: tableMonospaceFont, blockQuoteFont: tableFont, message: nil)
|
||||
|
||||
let format = senderName != nil ? presentationData.strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : presentationData.strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "")
|
||||
let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor)
|
||||
string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText)
|
||||
if let _ = senderPeerId {
|
||||
string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
|
||||
string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[1].range)
|
||||
} else {
|
||||
string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
|
||||
}
|
||||
value = string
|
||||
} else {
|
||||
let format = senderName != nil ? presentationData.strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : presentationData.strings.Gift_Unique_OriginalInfo(recipientName, dateString)
|
||||
let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor)
|
||||
if let _ = senderPeerId {
|
||||
string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
|
||||
string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[1].range)
|
||||
} else {
|
||||
string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
|
||||
}
|
||||
|
||||
value = string
|
||||
}
|
||||
|
||||
infoSize = self.infoView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: self.context,
|
||||
animationCache: self.context.animationCache,
|
||||
animationRenderer: self.context.animationRenderer,
|
||||
placeholderColor: self.presentationTheme.list.mediaPlaceholderColor,
|
||||
text: .plain(value),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
handleSpoilers: true
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: contentWidth - 64.0, height: size.height)
|
||||
)
|
||||
let infoFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - infoSize.width) / 2.0), y: titleSize.height + textSize.height + 54.0), size: infoSize)
|
||||
if let view = self.infoView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = infoFrame
|
||||
}
|
||||
self.infoBackgroundNode.frame = infoFrame.insetBy(dx: -12.0, dy: -12.0)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: titleSize.height + textSize.height + infoSize.height + actionsHeight + 46.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
do {
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
do {
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
do {
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if self.inProgress {
|
||||
let activityIndicator: ActivityIndicator
|
||||
if let current = self.activityIndicator {
|
||||
activityIndicator = current
|
||||
} else {
|
||||
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
|
||||
self.addSubnode(activityIndicator)
|
||||
}
|
||||
|
||||
if let actionNode = self.actionNodes.first {
|
||||
actionNode.isHidden = true
|
||||
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
import AlertComponent
|
||||
|
||||
public func giftRemoveInfoAlertController(
|
||||
context: AccountContext,
|
||||
@@ -309,32 +21,89 @@ public func giftRemoveInfoAlertController(
|
||||
removeInfoStars: Int64,
|
||||
navigationController: NavigationController?,
|
||||
commit: @escaping () -> Void
|
||||
) -> AlertController {
|
||||
) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
let title = strings.Gift_RemoveDetails_Title
|
||||
let text = strings.Gift_RemoveDetails_Text
|
||||
let buttonText = strings.Gift_RemoveDetails_Action(" $ \(presentationStringsFormattedNumber(Int32(clamping: removeInfoStars), presentationData.dateTimeFormat.groupingSeparator))").string
|
||||
|
||||
var contentNode: GiftRemoveInfoAlertContentNode?
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: {
|
||||
dismissImpl?(true)
|
||||
commit()
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
})]
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: strings.Gift_RemoveDetails_Title)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(strings.Gift_RemoveDetails_Text))
|
||||
)
|
||||
))
|
||||
|
||||
contentNode = GiftRemoveInfoAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peers: peers, title: title, text: text, actions: actions)
|
||||
|
||||
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: removeInfoStars > 0)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
for attribute in gift.attributes {
|
||||
if case let .originalInfo(senderPeerId, recipientPeerId, date, text, entities) = attribute {
|
||||
let textColor = presentationData.theme.actionSheet.primaryTextColor
|
||||
let linkColor = presentationData.theme.actionSheet.controlAccentColor
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let italicTextFont = Font.italic(15.0)
|
||||
let boldItalicTextFont = Font.with(size: 15.0, weight: .semibold, traits: .italic)
|
||||
let fixedTextFont = Font.monospace(15.0)
|
||||
|
||||
let senderName = senderPeerId.flatMap { peers[$0]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) }
|
||||
let recipientName = peers[recipientPeerId]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? ""
|
||||
|
||||
let dateString = stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: presentationData.dateTimeFormat, withTime: false)
|
||||
let value: NSAttributedString
|
||||
if let text {
|
||||
let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: boldItalicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil)
|
||||
|
||||
let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "")
|
||||
let string = NSMutableAttributedString(string: format.string, font: textFont, textColor: textColor)
|
||||
string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText)
|
||||
if let _ = senderPeerId {
|
||||
string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range)
|
||||
string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[1].range)
|
||||
} else {
|
||||
string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range)
|
||||
}
|
||||
value = string
|
||||
} else {
|
||||
let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : strings.Gift_Unique_OriginalInfo(recipientName, dateString)
|
||||
let string = NSMutableAttributedString(string: format.string, font: textFont, textColor: textColor)
|
||||
if let _ = senderPeerId {
|
||||
string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range)
|
||||
string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[1].range)
|
||||
} else {
|
||||
string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range)
|
||||
}
|
||||
|
||||
value = string
|
||||
}
|
||||
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "info",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .attributed(value), style: .background(.small))
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
return controller
|
||||
|
||||
let alertController = ChatMessagePaymentAlertController(
|
||||
context: context,
|
||||
presentationData: presentationData,
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: strings.Gift_RemoveDetails_Action(" $ \(presentationStringsFormattedNumber(Int32(clamping: removeInfoStars), presentationData.dateTimeFormat.groupingSeparator))").string, type: .default, action: {
|
||||
commit()
|
||||
}),
|
||||
.init(title: strings.Common_Cancel)
|
||||
],
|
||||
navigationController: navigationController,
|
||||
chatPeerId: context.account.peerId,
|
||||
showBalance: removeInfoStars > 0
|
||||
)
|
||||
return alertController
|
||||
}
|
||||
|
||||
+178
-427
@@ -6,421 +6,18 @@ import ComponentFlow
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
import GiftItemComponent
|
||||
import ChatMessagePaymentAlertController
|
||||
import ActivityIndicator
|
||||
import TooltipUI
|
||||
import MultilineTextComponent
|
||||
import TelegramStringFormatting
|
||||
|
||||
private final class GiftTransferAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private var presentationTheme: PresentationTheme
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let gift: StarGift.UniqueGift
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let giftView = ComponentView<Empty>()
|
||||
private let textNode: ASTextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let tableView = ComponentView<Empty>()
|
||||
|
||||
private let modelButtonTag = GenericComponentViewTag()
|
||||
private let backdropButtonTag = GenericComponentViewTag()
|
||||
private let symbolButtonTag = GenericComponentViewTag()
|
||||
|
||||
fileprivate var getController: () -> ViewController? = { return nil}
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
var inProgress = false {
|
||||
didSet {
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
ptheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
gift: StarGift.UniqueGift,
|
||||
peer: EnginePeer,
|
||||
title: String,
|
||||
text: String,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.presentationTheme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.gift = gift
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9))
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func dismissAllTooltips() {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
controller.window?.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
})
|
||||
controller.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func showAttributeInfo(tag: Any, text: String) {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
self.dismissAllTooltips()
|
||||
|
||||
guard let sourceView = self.tableView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
controller.present(tooltipController, in: .current)
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 310.0)
|
||||
|
||||
let strings = self.strings
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
|
||||
|
||||
let _ = self.giftView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationTheme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: avatarSize
|
||||
)
|
||||
if let view = self.giftView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = giftFrame
|
||||
}
|
||||
|
||||
if let arrowImage = self.arrowNode.image {
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
|
||||
origin.y += avatarSize.height + 17.0
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 5.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor
|
||||
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
|
||||
.model, .pattern, .backdrop, .originalInfo
|
||||
]
|
||||
|
||||
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
|
||||
for attribute in self.gift.attributes {
|
||||
attributeMap[attribute.attributeType] = attribute
|
||||
}
|
||||
|
||||
for type in order {
|
||||
if let attribute = attributeMap[type] {
|
||||
let id: String?
|
||||
let title: String?
|
||||
let value: NSAttributedString
|
||||
let percentage: Float?
|
||||
let tag: AnyObject?
|
||||
|
||||
switch attribute {
|
||||
case let .model(name, _, rarity):
|
||||
id = "model"
|
||||
title = strings.Gift_Unique_Model
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = self.modelButtonTag
|
||||
case let .backdrop(name, _, _, _, _, _, rarity):
|
||||
id = "backdrop"
|
||||
title = strings.Gift_Unique_Backdrop
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = self.backdropButtonTag
|
||||
case let .pattern(name, _, rarity):
|
||||
id = "pattern"
|
||||
title = strings.Gift_Unique_Symbol
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = self.symbolButtonTag
|
||||
case .originalInfo:
|
||||
continue
|
||||
}
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(value))
|
||||
)
|
||||
)
|
||||
)
|
||||
if let percentage, let tag {
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: self.context,
|
||||
text: formatPercentage(percentage),
|
||||
color: self.presentationTheme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
}
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
|
||||
tableItems.append(.init(
|
||||
id: id,
|
||||
title: title,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if let valueAmount = self.gift.valueAmount, let valueCurrency = self.gift.valueCurrency {
|
||||
tableItems.append(.init(
|
||||
id: "fiatValue",
|
||||
title: strings.Gift_Unique_Value,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "~\(formatCurrencyAmount(valueAmount, currency: valueCurrency))", font: tableFont, textColor: tableTextColor)))
|
||||
),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
||||
))
|
||||
}
|
||||
|
||||
let tableSize = self.tableView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
TableComponent(
|
||||
theme: self.presentationTheme,
|
||||
items: tableItems
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
|
||||
)
|
||||
let tableFrame = CGRect(origin: CGPoint(x: 16.0, y: avatarSize.height + titleSize.height + textSize.height + 60.0), size: tableSize)
|
||||
if let view = self.tableView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = tableFrame
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + 40.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
do {
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
do {
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
do {
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if self.inProgress {
|
||||
let activityIndicator: ActivityIndicator
|
||||
if let current = self.activityIndicator {
|
||||
activityIndicator = current
|
||||
} else {
|
||||
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
|
||||
self.addSubnode(activityIndicator)
|
||||
}
|
||||
|
||||
if let actionNode = self.actionNodes.first {
|
||||
actionNode.isUserInteractionEnabled = false
|
||||
actionNode.isHidden = false
|
||||
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
import AlertComponent
|
||||
import TableComponent
|
||||
import AvatarComponent
|
||||
import AlertTransferHeaderComponent
|
||||
import AlertTableComponent
|
||||
|
||||
public func giftTransferAlertController(
|
||||
context: AccountContext,
|
||||
@@ -429,7 +26,7 @@ public func giftTransferAlertController(
|
||||
transferStars: Int64,
|
||||
navigationController: NavigationController?,
|
||||
commit: @escaping () -> Void
|
||||
) -> AlertController {
|
||||
) -> AlertScreen {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
@@ -444,27 +41,181 @@ public func giftTransferAlertController(
|
||||
buttonText = strings.Gift_Transfer_Confirmation_TransferFree
|
||||
}
|
||||
|
||||
var contentNode: GiftTransferAlertContentNode?
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in
|
||||
contentNode?.inProgress = true
|
||||
commit()
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
})]
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = presentationData.theme.list.itemPrimaryTextColor
|
||||
|
||||
contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
|
||||
let modelButtonTag = GenericComponentViewTag()
|
||||
let backdropButtonTag = GenericComponentViewTag()
|
||||
let symbolButtonTag = GenericComponentViewTag()
|
||||
var showAttributeInfoImpl: ((Any, String) -> Void)?
|
||||
|
||||
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: transferStars > 0)
|
||||
contentNode?.getController = { [weak controller] in
|
||||
return controller
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
|
||||
.model, .pattern, .backdrop, .originalInfo
|
||||
]
|
||||
|
||||
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
|
||||
for attribute in gift.attributes {
|
||||
attributeMap[attribute.attributeType] = attribute
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
|
||||
for type in order {
|
||||
if let attribute = attributeMap[type] {
|
||||
let id: String?
|
||||
let title: String?
|
||||
let value: NSAttributedString
|
||||
let percentage: Float?
|
||||
let tag: AnyObject?
|
||||
|
||||
switch attribute {
|
||||
case let .model(name, _, rarity):
|
||||
id = "model"
|
||||
title = strings.Gift_Unique_Model
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = modelButtonTag
|
||||
case let .backdrop(name, _, _, _, _, _, rarity):
|
||||
id = "backdrop"
|
||||
title = strings.Gift_Unique_Backdrop
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = backdropButtonTag
|
||||
case let .pattern(name, _, rarity):
|
||||
id = "pattern"
|
||||
title = strings.Gift_Unique_Symbol
|
||||
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
|
||||
percentage = Float(rarity) * 0.1
|
||||
tag = symbolButtonTag
|
||||
case .originalInfo:
|
||||
continue
|
||||
}
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(value))
|
||||
)
|
||||
)
|
||||
)
|
||||
if let percentage, let tag {
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: context,
|
||||
text: formatPercentage(percentage),
|
||||
color: presentationData.theme.list.itemAccentColor
|
||||
)),
|
||||
action: {
|
||||
showAttributeInfoImpl?(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
}
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
|
||||
tableItems.append(.init(
|
||||
id: id,
|
||||
title: title,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
}
|
||||
return controller
|
||||
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "header",
|
||||
component: AnyComponent(
|
||||
AlertTransferHeaderComponent(
|
||||
fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
)),
|
||||
toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
peer: peer
|
||||
)
|
||||
)),
|
||||
type: .transfer
|
||||
)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: title)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(text))
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "table",
|
||||
component: AnyComponent(
|
||||
AlertTableComponent(items: tableItems)
|
||||
)
|
||||
))
|
||||
|
||||
let alertController = ChatMessagePaymentAlertController(
|
||||
context: context,
|
||||
presentationData: presentationData,
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: buttonText, type: .default, action: {
|
||||
commit()
|
||||
}),
|
||||
.init(title: strings.Common_Cancel)
|
||||
],
|
||||
navigationController: navigationController,
|
||||
chatPeerId: context.account.peerId,
|
||||
showBalance: transferStars > 0
|
||||
)
|
||||
|
||||
var dismissAllTooltipsImpl: (() -> Void)?
|
||||
showAttributeInfoImpl = { [weak alertController] tag, text in
|
||||
dismissAllTooltipsImpl?()
|
||||
guard let alertController, let sourceView = alertController.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: alertController.view) else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
alertController.present(tooltipController, in: .current)
|
||||
}
|
||||
dismissAllTooltipsImpl = { [weak alertController] in
|
||||
guard let alertController else {
|
||||
return
|
||||
}
|
||||
alertController.window?.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
})
|
||||
alertController.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return alertController
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ private final class SheetContent: CombinedComponent {
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ import LottieComponent
|
||||
import ProfileLevelRatingBarComponent
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import TableComponent
|
||||
|
||||
private final class GiftUpgradeCostScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
+1526
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ import GiftAnimationComponent
|
||||
import ContextUI
|
||||
import GiftItemComponent
|
||||
import GlassBarButtonComponent
|
||||
import TableComponent
|
||||
|
||||
private final class GiftValueSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@@ -640,7 +641,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak state] _ in
|
||||
|
||||
+256
-284
@@ -40,6 +40,10 @@ import ChatThemeScreen
|
||||
import ProfileLevelRatingBarComponent
|
||||
import AnimatedTextComponent
|
||||
import InfoParagraphComponent
|
||||
import ChatMessagePaymentAlertController
|
||||
import TableComponent
|
||||
import PeerTableCellComponent
|
||||
import AvatarComponent
|
||||
|
||||
private final class GiftViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@@ -120,6 +124,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let levelsDisposable = MetaDisposable()
|
||||
var nextGiftToUpgrade: ProfileGiftsContext.State.StarGift?
|
||||
|
||||
var giftVariantsDisposable = MetaDisposable()
|
||||
|
||||
var buyForm: BotPaymentForm?
|
||||
var buyFormDisposable: Disposable?
|
||||
var buyDisposable: Disposable?
|
||||
@@ -374,6 +380,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
self.buyDisposable?.dispose()
|
||||
self.levelsDisposable.dispose()
|
||||
self.starsTopUpOptionsDisposable?.dispose()
|
||||
self.giftVariantsDisposable.dispose()
|
||||
}
|
||||
|
||||
func openPeer(_ peer: EnginePeer, gifts: Bool = false, dismiss: Bool = true) {
|
||||
@@ -625,7 +632,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
controller?.dismissAnimated()
|
||||
|
||||
if let navigationController {
|
||||
Queue.mainQueue().after(0.5) {
|
||||
Queue.mainQueue().after(2.5) {
|
||||
starsContext.load(force: true)
|
||||
|
||||
let text: String
|
||||
@@ -730,7 +737,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().after(0.5) {
|
||||
Queue.mainQueue().after(2.5) {
|
||||
starsContext?.load(force: true)
|
||||
}
|
||||
switch self.subject {
|
||||
@@ -1134,7 +1141,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let peerId = peerIds.first else {
|
||||
return .complete()
|
||||
}
|
||||
Queue.mainQueue().after(1.5, {
|
||||
Queue.mainQueue().after(2.5, {
|
||||
if transferStars > 0 {
|
||||
context.starsContext?.load(force: true)
|
||||
}
|
||||
@@ -1350,6 +1357,33 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func openUpgradeVariants(attribute: StarGift.UniqueGift.Attribute? = nil) {
|
||||
guard let controller = self.getController() as? GiftViewScreen, let arguments = self.subject.arguments else {
|
||||
return
|
||||
}
|
||||
var selectedAttributes: [StarGift.UniqueGift.Attribute]?
|
||||
if case let .unique(uniqueGift) = arguments.gift {
|
||||
selectedAttributes = uniqueGift.attributes
|
||||
}
|
||||
|
||||
self.giftVariantsDisposable.set((self.context.engine.payments.getStarGiftUpgradeAttributes(giftId: arguments.gift.giftId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] attributes in
|
||||
guard let self, let attributes else {
|
||||
return
|
||||
}
|
||||
let variantsController = self.context.sharedContext.makeGiftUpgradeVariantsScreen(
|
||||
context: self.context,
|
||||
gift: arguments.gift,
|
||||
attributes: attributes,
|
||||
selectedAttributes: selectedAttributes,
|
||||
focusedAttribute: attribute
|
||||
)
|
||||
controller.push(variantsController)
|
||||
}))
|
||||
}
|
||||
|
||||
func showAttributeInfo(tag: Any, text: String) {
|
||||
guard let controller = self.getController() as? GiftViewScreen else {
|
||||
return
|
||||
@@ -1821,7 +1855,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
|
||||
Queue.mainQueue().after(0.5) {
|
||||
Queue.mainQueue().after(2.5) {
|
||||
switch finalPrice.currency {
|
||||
case .stars:
|
||||
context.starsContext?.load(force: true)
|
||||
@@ -2114,7 +2148,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
self.subject = .profileGift(peerId, result)
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
|
||||
Queue.mainQueue().after(0.5) {
|
||||
Queue.mainQueue().after(2.5) {
|
||||
starsContext?.load(force: true)
|
||||
}
|
||||
})
|
||||
@@ -2233,7 +2267,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().after(0.5) {
|
||||
Queue.mainQueue().after(2.5) {
|
||||
starsContext?.load(force: true)
|
||||
}
|
||||
|
||||
@@ -2337,6 +2371,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, case let .peerId(ownerPeerId) = uniqueGift.owner, let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: ownerPeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
@@ -2346,13 +2381,41 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration)
|
||||
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: peer.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let action: (Int64?) -> Void = { allowPaidStars in
|
||||
self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration, allowPaidStars: allowPaidStars)
|
||||
}
|
||||
if let sendPaidMessageStars, sendPaidMessageStars.value > 0 {
|
||||
let alertController = chatMessagePaymentAlertController(
|
||||
context: nil,
|
||||
presentationData: presentationData,
|
||||
updatedPresentationData: nil,
|
||||
peers: [EngineRenderedPeer(peer: peer)],
|
||||
count: 1,
|
||||
amount: sendPaidMessageStars,
|
||||
totalAmount: nil,
|
||||
hasCheck: false,
|
||||
navigationController: controller?.navigationController as? NavigationController,
|
||||
completion: { _ in
|
||||
action(sendPaidMessageStars.value)
|
||||
}
|
||||
)
|
||||
controller?.present(alertController, in: .window(.root))
|
||||
} else {
|
||||
action(nil)
|
||||
}
|
||||
})
|
||||
}))
|
||||
controller.push(buyController)
|
||||
})
|
||||
}
|
||||
|
||||
func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32) {
|
||||
func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) {
|
||||
guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
|
||||
return
|
||||
}
|
||||
@@ -2362,13 +2425,13 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: nil)
|
||||
self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: allowPaidStars)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
}, completed: { [weak self, weak starsContext] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().after(0.5) {
|
||||
Queue.mainQueue().after(2.5) {
|
||||
starsContext?.load(force: true)
|
||||
}
|
||||
self.openPeer(peer, dismiss: true)
|
||||
@@ -2383,11 +2446,15 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self, let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
var finalStars = price.amount.value
|
||||
if let allowPaidStars {
|
||||
finalStars += allowPaidStars
|
||||
}
|
||||
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
|
||||
context: context,
|
||||
starsContext: starsContext,
|
||||
options: options ?? [],
|
||||
purpose: .starGiftOffer(requiredStars: price.amount.value),
|
||||
purpose: .starGiftOffer(requiredStars: finalStars),
|
||||
targetPeerId: nil,
|
||||
customTheme: nil,
|
||||
completion: { [weak self, weak starsContext] stars in
|
||||
@@ -2411,7 +2478,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
self.inProgress = false
|
||||
self.updated()
|
||||
|
||||
self.commitGiftBuyOffer(peer: peer, price: price, duration: duration)
|
||||
self.commitGiftBuyOffer(peer: peer, price: price, duration: duration, allowPaidStars: allowPaidStars)
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
@@ -2480,10 +2547,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let upgradeNextButton = Child(PlainButtonComponent.self)
|
||||
|
||||
let upgradeTitle = Child(MultilineTextComponent.self)
|
||||
let upgradeDescription = Child(BalancedTextComponent.self)
|
||||
let upgradeDescription = Child(PlainButtonComponent.self)
|
||||
let upgradePerks = Child(List<Empty>.self)
|
||||
let upgradeKeepName = Child(PlainButtonComponent.self)
|
||||
let upgradePriceButton = Child(PlainButtonComponent.self)
|
||||
let variantsMeasureDescription = Child(MultilineTextComponent.self)
|
||||
|
||||
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
|
||||
|
||||
@@ -2687,10 +2755,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
headerSubject = .unique(state.justUpgraded ? state.upgradePreview?.attributes : nil, uniqueGift)
|
||||
} else if state.inUpgradePreview, let attributes = state.upgradePreview?.attributes {
|
||||
headerHeight = 258.0
|
||||
headerHeight = 246.0
|
||||
headerSubject = .preview(attributes)
|
||||
} else if case let .upgradePreview(attributes, _) = component.subject {
|
||||
headerHeight = 258.0
|
||||
headerHeight = 246.0
|
||||
headerSubject = .preview(attributes)
|
||||
} else if case let .wearPreview(_, attributes) = component.subject, let attributes {
|
||||
headerHeight = 200.0
|
||||
@@ -2944,9 +3012,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
originY += 16.0
|
||||
} else if showUpgradePreview {
|
||||
let title: String
|
||||
let description: String
|
||||
let uniqueText: String
|
||||
let transferableText: String
|
||||
let tradableText: String
|
||||
if !incoming, case let .profileGift(peerId, _) = subject, let peer = state.peerMap[peerId] {
|
||||
var peerName = peer.compactDisplayTitle
|
||||
@@ -2954,9 +3020,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
peerName = "\(peerName.prefix(22))…"
|
||||
}
|
||||
title = environment.strings.Gift_Upgrade_GiftTitle
|
||||
description = environment.strings.Gift_Upgrade_GiftDescription(peerName).string
|
||||
uniqueText = strings.Gift_Upgrade_Unique_GiftDescription(peerName).string
|
||||
transferableText = strings.Gift_Upgrade_Transferable_GiftDescription(peerName).string
|
||||
tradableText = strings.Gift_Upgrade_Tradable_GiftDescription(peerName).string
|
||||
} else if case let .upgradePreview(_, peerName) = component.subject {
|
||||
var peerName = peerName
|
||||
@@ -2964,15 +3028,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
peerName = "\(peerName.prefix(22))…"
|
||||
}
|
||||
title = environment.strings.Gift_Upgrade_IncludeTitle
|
||||
description = environment.strings.Gift_Upgrade_IncludeDescription(peerName).string
|
||||
uniqueText = strings.Gift_Upgrade_Unique_IncludeDescription
|
||||
transferableText = strings.Gift_Upgrade_Transferable_IncludeDescription
|
||||
tradableText = strings.Gift_Upgrade_Tradable_IncludeDescription
|
||||
} else {
|
||||
title = environment.strings.Gift_Upgrade_Title
|
||||
description = environment.strings.Gift_Upgrade_Description
|
||||
uniqueText = strings.Gift_Upgrade_Unique_Description
|
||||
transferableText = strings.Gift_Upgrade_Transferable_Description
|
||||
tradableText = strings.Gift_Upgrade_Tradable_Description
|
||||
}
|
||||
|
||||
@@ -2990,39 +3050,119 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
let upgradeDescription = upgradeDescription.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: description,
|
||||
font: Font.regular(13.0),
|
||||
textColor: .white,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 5,
|
||||
lineSpacing: 0.2,
|
||||
tintColor: vibrantColor
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let spacing: CGFloat = 6.0
|
||||
let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height
|
||||
|
||||
headerComponents.append({
|
||||
context.add(upgradeTitle
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0)))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
if case let .generic(gift) = component.subject.arguments?.gift, let upgradePreview = state.upgradePreview {
|
||||
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
|
||||
var i = 0
|
||||
for attribute in upgradePreview.attributes {
|
||||
if case .model = attribute {
|
||||
switch i {
|
||||
case 0:
|
||||
variant1 = .preview(attributes: [attribute], rarity: 0)
|
||||
case 1:
|
||||
variant2 = .preview(attributes: [attribute], rarity: 0)
|
||||
case 2:
|
||||
variant3 = .preview(attributes: [attribute], rarity: 0)
|
||||
default:
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
var buttonColor: UIColor = UIColor.white.withAlphaComponent(0.16)
|
||||
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||
buttonColor = previewPatternColor
|
||||
}
|
||||
|
||||
let variantsMeasureDescription = variantsMeasureDescription.update(
|
||||
component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_ViewAllVariants, font: Font.semibold(13.0), textColor: .clear))),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(variantsMeasureDescription
|
||||
.position(CGPoint(x: -10000.0, y: -10000.0))
|
||||
)
|
||||
|
||||
context.add(upgradeDescription
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0)))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
let upgradeDescription = upgradeDescription.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
ZStack([
|
||||
AnyComponentWithIdentity(id: "background", component: AnyComponent(
|
||||
FilledRoundedRectangleComponent(color: buttonColor, cornerRadius: .minEdge, smoothCorners: false)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: variant1,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: variant2,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: variant3,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "text", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_ViewAllVariants, font: Font.semibold(13.0), textColor: .white)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white)
|
||||
))
|
||||
], spacing: 3.0)))
|
||||
])
|
||||
),
|
||||
action: { [weak state] in
|
||||
state?.openUpgradeVariants()
|
||||
},
|
||||
animateScale: false
|
||||
),
|
||||
availableSize: CGSize(width: variantsMeasureDescription.size.width + 87.0, height: 24.0),
|
||||
transition: context.transition
|
||||
)
|
||||
})
|
||||
|
||||
let spacing: CGFloat = 6.0
|
||||
let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height
|
||||
|
||||
headerComponents.append({
|
||||
context.add(upgradeTitle
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(194.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0)))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
|
||||
context.add(upgradeDescription
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(198.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0)))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
})
|
||||
}
|
||||
originY += 24.0
|
||||
|
||||
let textColor = theme.actionSheet.primaryTextColor
|
||||
@@ -3044,20 +3184,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "transferable",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Upgrade_Transferable_Title,
|
||||
titleColor: textColor,
|
||||
text: transferableText,
|
||||
textColor: secondaryTextColor,
|
||||
accentColor: linkColor,
|
||||
iconName: "Premium/Collectible/Transferable",
|
||||
iconColor: linkColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "tradable",
|
||||
@@ -3072,6 +3198,20 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "wearable",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Upgrade_Wearable_Title,
|
||||
titleColor: textColor,
|
||||
text: strings.Gift_Upgrade_Wearable_Text,
|
||||
textColor: secondaryTextColor,
|
||||
accentColor: linkColor,
|
||||
iconName: "Premium/Collectible/Wearable",
|
||||
iconColor: linkColor
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
let perksSideInset = sideInset + 16.0
|
||||
let upgradePerks = upgradePerks.update(
|
||||
@@ -3564,7 +3704,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
@@ -3597,7 +3737,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
} else {
|
||||
ownerComponent = AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
@@ -3648,7 +3788,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
title: strings.Gift_Unique_Telegram,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
@@ -3678,7 +3818,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
@@ -3708,7 +3848,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
} else {
|
||||
fromComponent = AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
@@ -3733,7 +3873,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
id: "from_anon",
|
||||
title: strings.Gift_View_From,
|
||||
component: AnyComponent(
|
||||
PeerCellComponent(
|
||||
PeerTableCellComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
@@ -4117,7 +4257,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
|
||||
state?.openUpgradeVariants(attribute: attribute)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
@@ -5164,42 +5304,50 @@ final class GiftViewSheetComponent: CombinedComponent {
|
||||
|
||||
var headerContent: AnyComponent<Empty>?
|
||||
if let arguments = context.component.subject.arguments, case .unique = arguments.gift, let fromPeerId = arguments.fromPeerId, var fromPeerName = arguments.fromPeerName, arguments.fromPeerId != context.component.context.account.peerId && !(arguments.fromPeerId?.isTelegramNotifications ?? false) {
|
||||
let dateString = stringForMediumDate(timestamp: arguments.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, withTime: false)
|
||||
|
||||
if fromPeerName.count > 25 {
|
||||
fromPeerName = "\(fromPeerName.prefix(25))…"
|
||||
var showSenderInfo = false
|
||||
if arguments.incoming {
|
||||
showSenderInfo = true
|
||||
} else if arguments.peerId == context.component.context.account.peerId {
|
||||
showSenderInfo = true
|
||||
}
|
||||
if showSenderInfo {
|
||||
let dateString = stringForMediumDate(timestamp: arguments.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, withTime: false)
|
||||
|
||||
if fromPeerName.count > 25 {
|
||||
fromPeerName = "\(fromPeerName.prefix(25))…"
|
||||
}
|
||||
let rawString = environment.strings.Gift_View_SenderInfo(fromPeerName, dateString).string
|
||||
let attributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: .white), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), linkAttribute: { _ in return nil }))
|
||||
|
||||
let context = context.component.context
|
||||
headerContent = AnyComponent(
|
||||
PlainButtonComponent(content: AnyComponent(HeaderContentComponent(attributedText: attributedString)), action: {
|
||||
if let controller = controller(), let navigationController = controller.navigationController as? NavigationController {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: fromPeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak navigationController] peer in
|
||||
guard let peer, let navigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: context,
|
||||
chatLocation: .peer(peer),
|
||||
subject: nil,
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
keepStack: .always,
|
||||
useExisting: true,
|
||||
purposefulAction: nil,
|
||||
scrollToEndIfExists: false,
|
||||
activateMessageSearch: nil,
|
||||
animated: true
|
||||
))
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
let rawString = environment.strings.Gift_View_SenderInfo(fromPeerName, dateString).string
|
||||
let attributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: .white), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), linkAttribute: { _ in return nil }))
|
||||
|
||||
let context = context.component.context
|
||||
headerContent = AnyComponent(
|
||||
PlainButtonComponent(content: AnyComponent(HeaderContentComponent(attributedText: attributedString)), action: {
|
||||
if let controller = controller(), let navigationController = controller.navigationController as? NavigationController {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: fromPeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak navigationController] peer in
|
||||
guard let peer, let navigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: context,
|
||||
chatLocation: .peer(peer),
|
||||
subject: nil,
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
keepStack: .always,
|
||||
useExisting: true,
|
||||
purposefulAction: nil,
|
||||
scrollToEndIfExists: false,
|
||||
activateMessageSearch: nil,
|
||||
animated: true
|
||||
))
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
let sheet = sheet.update(
|
||||
@@ -5742,115 +5890,6 @@ func formatPercentage(_ value: Float) -> String {
|
||||
return String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + "%"
|
||||
}
|
||||
|
||||
final class PeerCellComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer?
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer?) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let avatarNode: AvatarNode
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private var component: PeerCellComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let avatarSize = CGSize(width: 22.0, height: 22.0)
|
||||
let spacing: CGFloat = 6.0
|
||||
|
||||
var peerName: String
|
||||
let avatarOverride: AvatarNodeImageOverride?
|
||||
if let peerValue = component.peer {
|
||||
peerName = peerValue.compactDisplayTitle
|
||||
if peerName.count > 40 {
|
||||
peerName = "\(peerName.prefix(40))…"
|
||||
}
|
||||
avatarOverride = nil
|
||||
} else {
|
||||
peerName = component.strings.Gift_View_HiddenName
|
||||
avatarOverride = .anonymousSavedMessagesIcon(isColored: true)
|
||||
}
|
||||
|
||||
let avatarNaturalSize = CGSize(width: 40.0, height: 40.0)
|
||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride)
|
||||
self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left))
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height)
|
||||
)
|
||||
|
||||
let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height)
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize)
|
||||
self.avatarNode.frame = avatarFrame
|
||||
|
||||
if let view = self.text.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
transition.setFrame(view: view, frame: textFrame)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class HeaderContentComponent: Component {
|
||||
let attributedText: NSAttributedString
|
||||
|
||||
@@ -6165,73 +6204,6 @@ private final class HeaderButtonComponent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
final class AvatarComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
}
|
||||
|
||||
static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let avatarNode: AvatarNode
|
||||
|
||||
private var component: AvatarComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 42.0))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: .zero, size: availableSize)
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
peer: component.peer,
|
||||
synchronousLoad: true
|
||||
)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private struct GiftViewConfiguration {
|
||||
public static var defaultValue: GiftViewConfiguration {
|
||||
return GiftViewConfiguration(explorerUrl: "https://tonviewer.com")
|
||||
|
||||
+184
-353
@@ -14,391 +14,222 @@ import Markdown
|
||||
import GiftItemComponent
|
||||
import StarsAvatarComponent
|
||||
import PasswordSetupUI
|
||||
import OwnershipTransferController
|
||||
import PresentationDataUtils
|
||||
import AlertComponent
|
||||
import AlertTransferHeaderComponent
|
||||
import AlertInputFieldComponent
|
||||
|
||||
private final class GiftWithdrawAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private var presentationTheme: PresentationTheme
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let gift: StarGift.UniqueGift
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let giftView = ComponentView<Empty>()
|
||||
private let textNode: ASTextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let avatarView = ComponentView<Empty>()
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
ptheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
gift: StarGift.UniqueGift,
|
||||
title: String,
|
||||
text: String,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.presentationTheme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.gift = gift
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
|
||||
|
||||
let _ = self.giftView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationTheme,
|
||||
strings: self.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: avatarSize
|
||||
)
|
||||
if let view = self.giftView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = giftFrame
|
||||
}
|
||||
|
||||
if let arrowImage = self.arrowNode.image {
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
|
||||
let _ = self.avatarView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
StarsAvatarComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationTheme,
|
||||
peer: .transactionPeer(.fragment),
|
||||
photo: nil,
|
||||
media: [],
|
||||
gift: nil,
|
||||
backgroundColor: .clear,
|
||||
size: avatarSize
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: avatarSize
|
||||
)
|
||||
if let view = self.avatarView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = avatarFrame
|
||||
}
|
||||
|
||||
origin.y += avatarSize.height + 17.0
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 5.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.vertical
|
||||
if !"".isEmpty {
|
||||
// Silence the warning
|
||||
effectiveActionLayout = .horizontal
|
||||
}
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 24.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
public func giftWithdrawAlertController(context: AccountContext, gift: StarGift.UniqueGift, commit: @escaping () -> Void) -> AlertController {
|
||||
public func giftWithdrawAlertController(
|
||||
context: AccountContext,
|
||||
gift: StarGift.UniqueGift,
|
||||
commit: @escaping () -> Void
|
||||
) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "header",
|
||||
component: AnyComponent(
|
||||
AlertTransferHeaderComponent(
|
||||
fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
)),
|
||||
toComponent: AnyComponentWithIdentity(id: "fragment", component: AnyComponent(
|
||||
StarsAvatarComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
peer: .transactionPeer(.fragment),
|
||||
photo: nil,
|
||||
media: [],
|
||||
gift: nil,
|
||||
backgroundColor: .clear,
|
||||
size: CGSize(width: 60.0, height: 60.0)
|
||||
)
|
||||
)),
|
||||
type: .transfer
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
let title = strings.Gift_Withdraw_Title
|
||||
let text = strings.Gift_Withdraw_Text("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))").string
|
||||
let buttonText = strings.Gift_Withdraw_Proceed
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: {
|
||||
dismissImpl?(true)
|
||||
commit()
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
})]
|
||||
|
||||
let contentNode = GiftWithdrawAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, title: title, text: text, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
return controller
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: strings.Gift_Withdraw_Title)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(strings.Gift_Withdraw_Text("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))").string))
|
||||
)
|
||||
))
|
||||
|
||||
let alertController = AlertScreen(
|
||||
context: context,
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: strings.Gift_Withdraw_Proceed, type: .default, action: {
|
||||
commit()
|
||||
}),
|
||||
.init(title: strings.Common_Cancel)
|
||||
]
|
||||
)
|
||||
return alertController
|
||||
}
|
||||
|
||||
public func confirmGiftWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, reference: StarGiftReference, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController {
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
public func confirmGiftWithdrawalController(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
reference: StarGiftReference,
|
||||
present: @escaping (ViewController, Any?) -> Void,
|
||||
completion: @escaping (String) -> Void
|
||||
) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
let inputState = AlertInputFieldComponent.ExternalState()
|
||||
|
||||
let doneIsEnabled: Signal<Bool, NoError> = inputState.valueSignal
|
||||
|> map { value in
|
||||
return !value.isEmpty
|
||||
}
|
||||
|
||||
let doneInProgressPromise = ValuePromise<Bool>(false)
|
||||
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: strings.Gift_Withdraw_EnterPassword_Title)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(strings.Gift_Withdraw_EnterPassword_Text))
|
||||
)
|
||||
))
|
||||
|
||||
var applyImpl: (() -> Void)?
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "input",
|
||||
component: AnyComponent(
|
||||
AlertInputFieldComponent(
|
||||
context: context,
|
||||
placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder,
|
||||
isSecureTextEntry: true,
|
||||
isInitiallyFocused: true,
|
||||
externalState: inputState,
|
||||
returnKeyAction: {
|
||||
applyImpl?()
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
|
||||
if let updatedPresentationData {
|
||||
effectiveUpdatedPresentationData = updatedPresentationData
|
||||
} else {
|
||||
effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var proceedImpl: (() -> Void)?
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.Gift_Withdraw_EnterPassword_Title, text: presentationData.strings.Gift_Withdraw_EnterPassword_Text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Gift_Withdraw_EnterPassword_Done, action: {
|
||||
proceedImpl?()
|
||||
})])
|
||||
|
||||
contentNode.complete = {
|
||||
proceedImpl?()
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
contentNode?.theme = presentationData.theme
|
||||
})
|
||||
controller.dismissed = { _ in
|
||||
presentationDataDisposable.dispose()
|
||||
disposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller, weak contentNode] in
|
||||
contentNode?.dismissInput()
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
proceedImpl = { [weak contentNode] in
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
contentNode.updateIsChecking(true)
|
||||
|
||||
let signal = context.engine.payments.requestStarGiftWithdrawalUrl(reference: reference, password: contentNode.password)
|
||||
disposable.set((signal |> deliverOnMainQueue).start(next: { url in
|
||||
let alertController = AlertScreen(
|
||||
configuration: AlertScreen.Configuration(allowInputInset: true),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: strings.Common_Cancel),
|
||||
.init(title: strings.Gift_Withdraw_EnterPassword_Done, type: .default, action: {
|
||||
applyImpl?()
|
||||
}, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get())
|
||||
],
|
||||
updatedPresentationData: effectiveUpdatedPresentationData
|
||||
)
|
||||
applyImpl = {
|
||||
doneInProgressPromise.set(true)
|
||||
|
||||
let _ = (context.engine.payments.requestStarGiftWithdrawalUrl(reference: reference, password: inputState.value)
|
||||
|> deliverOnMainQueue).start(next: { url in
|
||||
dismissImpl?()
|
||||
completion(url)
|
||||
}, error: { [weak contentNode] error in
|
||||
}, error: { error in
|
||||
var errorTextAndActions: (String, [TextAlertAction])?
|
||||
switch error {
|
||||
case .invalidPassword:
|
||||
contentNode?.animateError()
|
||||
case .limitExceeded:
|
||||
errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
default:
|
||||
errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
case .invalidPassword:
|
||||
inputState.animateError()
|
||||
case .limitExceeded:
|
||||
errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
|
||||
default:
|
||||
errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
|
||||
}
|
||||
contentNode?.updateIsChecking(false)
|
||||
|
||||
doneInProgressPromise.set(false)
|
||||
|
||||
if let (text, actions) = errorTextAndActions {
|
||||
dismissImpl?()
|
||||
present(textAlertController(context: context, title: nil, text: text, actions: actions), nil)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
return controller
|
||||
dismissImpl = { [weak alertController] in
|
||||
alertController?.dismiss(completion: nil)
|
||||
}
|
||||
return alertController
|
||||
}
|
||||
|
||||
public func giftWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, reference: StarGiftReference, initialError: RequestStarGiftWithdrawalError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController {
|
||||
public func giftWithdrawalController(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
reference: StarGiftReference,
|
||||
initialError: RequestStarGiftWithdrawalError,
|
||||
present: @escaping (ViewController, Any?) -> Void,
|
||||
completion: @escaping (String) -> Void
|
||||
) -> ViewController {
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = AlertControllerTheme(presentationData: presentationData)
|
||||
let strings = presentationData.strings
|
||||
|
||||
var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.Gift_Withdraw_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
var title: String? = strings.Gift_Withdraw_SecurityCheck
|
||||
var text = strings.Gift_Withdraw_SecurityRequirements
|
||||
|
||||
var text = presentationData.strings.Gift_Withdraw_SecurityRequirements
|
||||
let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0
|
||||
|
||||
var actions: [TextAlertAction] = []
|
||||
var actions: [AlertScreen.Action] = [
|
||||
.init(title: strings.Common_OK, type: .default)
|
||||
]
|
||||
switch initialError {
|
||||
case .requestPassword:
|
||||
return confirmGiftWithdrawalController(context: context, updatedPresentationData: updatedPresentationData, reference: reference, present: present, completion: completion)
|
||||
case .twoStepAuthTooFresh, .authSessionTooFresh:
|
||||
text = text + presentationData.strings.Gift_Withdraw_ComeBackLater
|
||||
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
|
||||
case .twoStepAuthMissing:
|
||||
actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.Gift_Withdraw_SetupTwoStepAuth, action: {
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
|
||||
if shouldDismiss {
|
||||
controller.dismiss()
|
||||
}
|
||||
})
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})]
|
||||
actions = [
|
||||
.init(title: strings.Gift_Withdraw_SetupTwoStepAuth, type: .default, action: {
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
|
||||
if shouldDismiss {
|
||||
controller.dismiss()
|
||||
}
|
||||
})
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}),
|
||||
.init(title: strings.Common_Cancel)
|
||||
]
|
||||
default:
|
||||
title = nil
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
|
||||
text = strings.Login_UnknownError
|
||||
}
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||
|
||||
return richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
|
||||
return AlertScreen(
|
||||
context: context,
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
|
||||
title: title,
|
||||
text: text,
|
||||
actions: actions
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user