mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
c509a6e1a2
commit
cbe98a3fc6
15 changed files with 528 additions and 364 deletions
|
|
@ -15672,7 +15672,7 @@ Error: %8$@";
|
|||
|
||||
"Appearance.SendWithCmdEnter" = "Send Messages with ⌘+Enter";
|
||||
|
||||
"Notification.StarsGift.Crafted" = "You crafted a new unique collectible";
|
||||
"Notification.StarsGift.Crafted" = "You crafted a new gift";
|
||||
|
||||
"Gift.Craft.Title" = "Craft Gift";
|
||||
"Gift.Craft.Description" = "Add up to **4 gifts** to craft new\n**$ %@**.\n\nIf crafting fails, all used gifts\nwill be lost.";
|
||||
|
|
@ -15757,3 +15757,11 @@ Error: %8$@";
|
|||
"Notification.GroupCreatorChangeApplied" = "%1$@ has transferred ownership of the group to %2$@.";
|
||||
|
||||
"Notification.StarGift.Crafted.Title" = "Crafted Gift";
|
||||
"Notification.StarGift.Upgraded.Title" = "Upgraded Gift";
|
||||
"Notification.StarGift.Purchased.Title" = "Purchased Gift";
|
||||
|
||||
"LeaveGroup.Title" = "Leave %@";
|
||||
"LeaveGroup.Text" = "If you leave, **%1$@** will become the owner of **%2$@** in **1 week**.";
|
||||
"LeaveGroup.AppointAnotherOwner" = "Appoint Another Owner";
|
||||
"LeaveGroup.Proceed" = "Leave Group";
|
||||
"AppointAnotherOwner.Title" = "Appoint Another Owner";
|
||||
|
|
|
|||
|
|
@ -51,17 +51,16 @@ extension ChatListControllerImpl {
|
|||
)
|
||||
)
|
||||
))
|
||||
//TODO:localize
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: "Leave \(peer.compactDisplayTitle)")
|
||||
AlertTitleComponent(title: self.presentationData.strings.LeaveGroup_Title(peer.compactDisplayTitle).string)
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain("If you leave, **\(nextCreator.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))** will become the owner of **\(peer.compactDisplayTitle)** in **1 week**."))
|
||||
AlertTextComponent(content: .plain(self.presentationData.strings.LeaveGroup_Text(nextCreator.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), peer.compactDisplayTitle).string))
|
||||
)
|
||||
))
|
||||
|
||||
|
|
@ -70,16 +69,18 @@ extension ChatListControllerImpl {
|
|||
configuration: .init(actionAlignment: .vertical),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: "Appoint Another Owner", action: { [weak self] in
|
||||
.init(title: self.presentationData.strings.LeaveGroup_AppointAnotherOwner, action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentOwnershipTransfer(chatPeer: peer)
|
||||
self.presentOwnershipTransfer(chatPeer: peer, leaveGroup: {
|
||||
completion(true)
|
||||
})
|
||||
}),
|
||||
.init(title: "Cancel", action: {
|
||||
.init(title: self.presentationData.strings.Common_Cancel, action: {
|
||||
completion(false)
|
||||
}),
|
||||
.init(title: "Leave Group", type: .destructive, action: {
|
||||
.init(title: self.presentationData.strings.LeaveGroup_Proceed, type: .destructive, action: {
|
||||
completion(true)
|
||||
})
|
||||
]
|
||||
|
|
@ -90,7 +91,7 @@ extension ChatListControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
func presentOwnershipTransfer(chatPeer: EnginePeer) {
|
||||
func presentOwnershipTransfer(chatPeer: EnginePeer, leaveGroup: @escaping () -> Void) {
|
||||
let presentController: (ViewController) -> Void = { [weak self] c in
|
||||
if let topController = self?.navigationController?.topViewController as? ViewController {
|
||||
topController.present(c, in: .window(.root))
|
||||
|
|
@ -121,7 +122,9 @@ extension ChatListControllerImpl {
|
|||
case .creator:
|
||||
return
|
||||
case let .member(_, _, adminInfo, _, _, _):
|
||||
let _ = adminInfo
|
||||
if adminInfo == nil {
|
||||
let _ = self.context.engine.peers.updateChannelAdminRights(peerId: chatPeer.id, adminId: peer.id, rights: TelegramChatAdminRights(rights: .all), rank: nil).start()
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.peers.checkOwnershipTranfserAvailability(memberId: peer.id) |> deliverOnMainQueue).start(error: { [weak self] error in
|
||||
guard let self, case let .user(user) = peer else {
|
||||
|
|
@ -141,6 +144,8 @@ extension ChatListControllerImpl {
|
|||
},
|
||||
completion: { _ in
|
||||
dismissController?()
|
||||
|
||||
leaveGroup()
|
||||
}
|
||||
)
|
||||
presentController(controller)
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMe
|
|||
let title: String
|
||||
switch params.mode {
|
||||
case .ownershipTransfer:
|
||||
//TODO:localize
|
||||
title = "Appoint Another Owner"
|
||||
title = self.presentationData.strings.AppointAnotherOwner_Title
|
||||
let titleView = CounterControllerTitleView(theme: self.presentationData.theme)
|
||||
titleView.title = CounterControllerTitle(title: title, counter: " ")
|
||||
self.navigationItem.titleView = titleView
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 224
|
||||
return 222
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ public final class AlertTitleComponent: Component {
|
|||
|
||||
let environment = environment[AlertComponentEnvironment.self]
|
||||
|
||||
let inset: CGFloat = -6.0
|
||||
let titleConstrainedSize = CGSize(width: availableSize.width - inset * 2.0, height: availableSize.height)
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
|
|
@ -77,13 +79,13 @@ public final class AlertTitleComponent: Component {
|
|||
maximumNumberOfLines: 0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
containerSize: titleConstrainedSize
|
||||
)
|
||||
|
||||
let titleOriginX: CGFloat
|
||||
switch component.alignment {
|
||||
case .default:
|
||||
titleOriginX = 0.0
|
||||
titleOriginX = inset
|
||||
case .center:
|
||||
titleOriginX = floorToScreenPixels((availableSize.width - titleSize.width) / 2.0)
|
||||
}
|
||||
|
|
@ -280,7 +282,8 @@ public final class AlertTextComponent: Component {
|
|||
break
|
||||
}
|
||||
|
||||
let textConstrainedSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||
let inset: CGFloat = -6.0
|
||||
let textConstrainedSize = CGSize(width: availableSize.width - inset * 2.0, height: availableSize.height)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: transition,
|
||||
|
|
@ -314,7 +317,7 @@ public final class AlertTextComponent: Component {
|
|||
containerSize: textConstrainedSize
|
||||
)
|
||||
|
||||
var textOffset = CGPoint()
|
||||
var textOffset = CGPoint(x: inset, y: 0.0)
|
||||
if hasCenterAlignment {
|
||||
textOffset.x = floorToScreenPixels((availableSize.width - textSize.width) / 2.0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -581,7 +581,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
|
||||
let isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel || channelPeerId != nil
|
||||
if isSelfGift {
|
||||
title = item.presentationData.strings.Notification_StarGift_Self_Title
|
||||
title = item.presentationData.strings.Notification_StarGift_Purchased_Title
|
||||
} else {
|
||||
if isPrepaidUpgrade && senderPeerId == channelPeerId {
|
||||
title = item.presentationData.strings.Gift_View_Unknown_Title
|
||||
|
|
@ -714,8 +714,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
} else if isSelfGift {
|
||||
if isCrafted {
|
||||
title = item.presentationData.strings.Notification_StarGift_Crafted_Title
|
||||
} else if isUpgrade {
|
||||
title = item.presentationData.strings.Notification_StarGift_Upgraded_Title
|
||||
} else {
|
||||
title = item.presentationData.strings.Notification_StarGift_Self_Title
|
||||
title = item.presentationData.strings.Notification_StarGift_Purchased_Title
|
||||
}
|
||||
} else if item.message.id.peerId.isTelegramNotifications {
|
||||
title = item.presentationData.strings.Notification_StarGift_TitleShort
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ swift_library(
|
|||
"//submodules/TooltipUI",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftStoreScreen",
|
||||
"//submodules/TelegramUI/Components/SpaceWarpView",
|
||||
"//submodules/ConfettiEffect",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ final class DialIndicatorComponent: Component {
|
|||
value: Int,
|
||||
suffix: String,
|
||||
isVisible: Bool = true,
|
||||
isFlipped: Bool = false,
|
||||
isFlipped: Bool = false
|
||||
) {
|
||||
self.content = content
|
||||
self.backgroundColor = backgroundColor
|
||||
|
|
@ -224,3 +224,72 @@ final class DialIndicatorComponent: Component {
|
|||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class ColorSwatchComponent: Component {
|
||||
let innerColor: UIColor
|
||||
let outerColor: UIColor
|
||||
|
||||
public init(
|
||||
innerColor: UIColor,
|
||||
outerColor: UIColor
|
||||
) {
|
||||
self.innerColor = innerColor
|
||||
self.outerColor = outerColor
|
||||
}
|
||||
|
||||
public static func ==(lhs: ColorSwatchComponent, rhs: ColorSwatchComponent) -> Bool {
|
||||
if lhs.innerColor != rhs.innerColor {
|
||||
return false
|
||||
}
|
||||
if lhs.outerColor != rhs.outerColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIImageView {
|
||||
private var component: ColorSwatchComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ColorSwatchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if previousComponent?.innerColor != component.innerColor || previousComponent?.outerColor != component.outerColor {
|
||||
self.image = generateImage(availableSize, contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
if let image = UIImage(bundleImageName: "Premium/Craft/DialColorMask"), let cgImage = image.cgImage {
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
}
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [component.innerColor.cgColor, component.outerColor.cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,13 +31,9 @@ import LottieComponent
|
|||
import TooltipUI
|
||||
import TextFormat
|
||||
import GlassBackgroundComponent
|
||||
import SpaceWarpView
|
||||
import ConfettiEffect
|
||||
import TelegramNotices
|
||||
|
||||
private let backdropButtonTag = GenericComponentViewTag()
|
||||
private let symbolButtonTag = GenericComponentViewTag()
|
||||
|
||||
private final class CraftGiftPageContent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
|
|
@ -145,9 +141,9 @@ private final class CraftGiftPageContent: Component {
|
|||
|
||||
private var craftTable = ComponentView<Empty>()
|
||||
|
||||
private var backdropDial = ComponentView<Empty>()
|
||||
private var symbolDial = ComponentView<Empty>()
|
||||
private var variantsButton = ComponentView<Empty>()
|
||||
private var attributeDials: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private var attributeDialTags: [AnyHashable: GenericComponentViewTag] = [:]
|
||||
private var variantsButton: ComponentView<Empty>?
|
||||
private var variantsButtonMeasure = ComponentView<Empty>()
|
||||
|
||||
private let craftingTitle = ComponentView<Empty>()
|
||||
|
|
@ -461,282 +457,8 @@ private final class CraftGiftPageContent: Component {
|
|||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
craftContentHeight += 291.0
|
||||
craftContentHeight += descriptionTextSize.height
|
||||
craftContentHeight += 25.0
|
||||
|
||||
var attributes: [ResaleGiftsContext.Attribute: StarGift.UniqueGift.Attribute] = [:]
|
||||
var backdropAttributeCount: [ResaleGiftsContext.Attribute: Int32] = [:]
|
||||
var patternAttributeCount: [ResaleGiftsContext.Attribute: Int32] = [:]
|
||||
for gift in selectedGifts.values {
|
||||
for attribute in gift.gift.attributes {
|
||||
switch attribute {
|
||||
case let .backdrop(_, id, _, _, _, _, _):
|
||||
let attributeId: ResaleGiftsContext.Attribute = .backdrop(id)
|
||||
attributes[attributeId] = attribute
|
||||
if let count = backdropAttributeCount[attributeId] {
|
||||
backdropAttributeCount[attributeId] = count + 1
|
||||
} else {
|
||||
backdropAttributeCount[attributeId] = 1
|
||||
}
|
||||
case let .pattern(_, file, _):
|
||||
let attributeId: ResaleGiftsContext.Attribute = .pattern(file.fileId.id)
|
||||
attributes[attributeId] = attribute
|
||||
if let count = patternAttributeCount[attributeId] {
|
||||
patternAttributeCount[attributeId] = count + 1
|
||||
} else {
|
||||
patternAttributeCount[attributeId] = 1
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mostFrequentAttribute(from counts: [ResaleGiftsContext.Attribute: Int32]) -> (attribute: StarGift.UniqueGift.Attribute, count: Int32)? {
|
||||
guard let (id, count) = counts.max(by: { $0.value < $1.value }),
|
||||
count > 1,
|
||||
let attribute = attributes[id] else {
|
||||
return nil
|
||||
}
|
||||
return (attribute, count)
|
||||
}
|
||||
|
||||
var possibleBackdrop: (StarGift.UniqueGift.Attribute, Int32)?
|
||||
var possiblePattern: (StarGift.UniqueGift.Attribute, Int32)?
|
||||
|
||||
var backdropColor: UIColor = .white
|
||||
var backdropPermille: Int = 0
|
||||
var backdropName = ""
|
||||
var symbolFile: TelegramMediaFile?
|
||||
var symbolPermille: Int = 0
|
||||
var symbolName = ""
|
||||
|
||||
for attribute in component.gift.attributes {
|
||||
switch attribute {
|
||||
case .backdrop:
|
||||
possibleBackdrop = (attribute, 1)
|
||||
case .pattern:
|
||||
possiblePattern = (attribute, 1)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let betterBackdrop = mostFrequentAttribute(from: backdropAttributeCount) {
|
||||
possibleBackdrop = betterBackdrop
|
||||
}
|
||||
|
||||
if let betterPattern = mostFrequentAttribute(from: patternAttributeCount) {
|
||||
possiblePattern = betterPattern
|
||||
}
|
||||
|
||||
let appConfiguration = component.context.currentAppConfiguration.with { $0 }
|
||||
let giftCraftConfiguration = GiftCraftConfiguration.with(appConfiguration: appConfiguration)
|
||||
|
||||
if case let .backdrop(name, _, innerColor, _, _, _, _) = possibleBackdrop?.0 {
|
||||
backdropColor = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||
backdropName = name
|
||||
}
|
||||
if let possibleBackdrop {
|
||||
backdropPermille = Int(giftCraftConfiguration.craftAttributePermilles[Int(possibleBackdrop.1 - 1)])
|
||||
}
|
||||
|
||||
if case let .pattern(name, file, _) = possiblePattern?.0 {
|
||||
symbolFile = file
|
||||
symbolName = name
|
||||
}
|
||||
if let possiblePattern {
|
||||
symbolPermille = Int(giftCraftConfiguration.craftAttributePermilles[Int(possiblePattern.1 - 1)])
|
||||
}
|
||||
|
||||
let backdropDialSize = self.backdropDial.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
DialIndicatorComponent(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "color",
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(name: "Components/ColorMask", tintColor: backdropColor)
|
||||
)
|
||||
),
|
||||
backgroundColor: .white.withAlphaComponent(0.1),
|
||||
foregroundColor: .white,
|
||||
diameter: 48.0,
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
progress: CGFloat(backdropPermille) / 10.0 / 100.0,
|
||||
value: backdropPermille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
#if DEBUG
|
||||
self.component?.externalState.testFailOrSuccess = true
|
||||
#endif
|
||||
self.showAttributeInfo(tag: backdropButtonTag, text: environment.strings.Gift_Craft_BackdropTooltip("\(backdropPermille / 10)", backdropName).string)
|
||||
},
|
||||
tag: backdropButtonTag
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let symbolDialSize = self.symbolDial.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
DialIndicatorComponent(
|
||||
content: symbolFile.flatMap { AnyComponentWithIdentity(
|
||||
id: "symbol",
|
||||
component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.ResourceContent(
|
||||
context: component.context,
|
||||
file: $0,
|
||||
attemptSynchronously: true,
|
||||
providesPlaceholder: true
|
||||
),
|
||||
color: .white,
|
||||
size: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
)
|
||||
) } ?? AnyComponentWithIdentity(
|
||||
id: "empty", component: AnyComponent(Rectangle(color: .clear))),
|
||||
backgroundColor: .white.withAlphaComponent(0.1),
|
||||
foregroundColor: .white,
|
||||
diameter: 48.0,
|
||||
contentSize: CGSize(width: 28.0, height: 28.0),
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
progress: CGFloat(symbolPermille) / 10.0 / 100.0,
|
||||
value: symbolPermille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
#if DEBUG
|
||||
self.component?.externalState.testFailOrSuccess = false
|
||||
#endif
|
||||
self.showAttributeInfo(tag: symbolButtonTag, text: environment.strings.Gift_Craft_SymbolTooltip("\(symbolPermille / 10)", symbolName).string)
|
||||
},
|
||||
tag: symbolButtonTag
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
craftContentHeight += backdropDialSize.height
|
||||
craftContentHeight += 15.0
|
||||
|
||||
let variantsString = environment.strings.Gift_Craft_ViewVariants
|
||||
let variantsButtonMeasure = self.variantsButtonMeasure.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .clear)))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let variantsButtonSize = CGSize(width: variantsButtonMeasure.width + 87.0, height: 24.0)
|
||||
if let gift = self.starGiftsMap[component.gift.giftId] {
|
||||
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
|
||||
if let upgradePreview = self.upgradePreview {
|
||||
var i = 0
|
||||
for attribute in upgradePreview {
|
||||
if case .model = attribute {
|
||||
switch i {
|
||||
case 0:
|
||||
variant1 = .preview(attributes: [attribute], rarity: nil)
|
||||
case 1:
|
||||
variant2 = .preview(attributes: [attribute], rarity: nil)
|
||||
case 2:
|
||||
variant3 = .preview(attributes: [attribute], rarity: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.variantsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: variantsButtonSize,
|
||||
backgroundColor: component.colors.3,
|
||||
isDark: true,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "content", component: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: variant1,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: variant2,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: variant3,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "text", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .white)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white)
|
||||
))
|
||||
], spacing: 3.0))),
|
||||
action: { [weak self] _ in
|
||||
self?.openUpgradeVariants()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
}
|
||||
craftContentHeight += 160.0
|
||||
|
||||
let originalCraftContentHeight = craftContentHeight
|
||||
if !"".isEmpty, isCrafting {
|
||||
craftContentHeight = component.screenSize.height
|
||||
}
|
||||
|
||||
let descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionTextSize.width) * 0.5), y: craftContentHeight - 145.0 - 78.0 - 115.0), size: descriptionTextSize)
|
||||
let descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionTextSize.width) * 0.5), y: craftContentHeight), size: descriptionTextSize)
|
||||
if let descriptionTextView = self.descriptionText.view {
|
||||
if descriptionTextView.superview == nil {
|
||||
self.addSubview(descriptionTextView)
|
||||
|
|
@ -745,52 +467,395 @@ private final class CraftGiftPageContent: Component {
|
|||
transition.setAlpha(view: descriptionTextView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: descriptionTextView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
craftContentHeight += descriptionTextSize.height
|
||||
craftContentHeight += 16.0
|
||||
|
||||
let backdropDialFrame = CGRect(origin: CGPoint(x: availableSize.width * 0.5 - 9.0 - backdropDialSize.width, y: craftContentHeight - 145.0 - 78.0), size: backdropDialSize)
|
||||
if let backdropDialView = self.backdropDial.view {
|
||||
if backdropDialView.superview == nil {
|
||||
self.addSubview(backdropDialView)
|
||||
}
|
||||
transition.setFrame(view: backdropDialView, frame: backdropDialFrame)
|
||||
transition.setAlpha(view: backdropDialView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: backdropDialView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
let symbolDialFrame = CGRect(origin: CGPoint(x: availableSize.width * 0.5 + 9.0, y: craftContentHeight - 145.0 - 78.0), size: symbolDialSize)
|
||||
if let symbolDialView = self.symbolDial.view {
|
||||
if symbolDialView.superview == nil {
|
||||
self.addSubview(symbolDialView)
|
||||
}
|
||||
transition.setFrame(view: symbolDialView, frame: symbolDialFrame)
|
||||
transition.setAlpha(view: symbolDialView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: symbolDialView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
let variantsButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - variantsButtonSize.width) / 2.0), y: craftContentHeight - 145.0), size: variantsButtonSize)
|
||||
var varitantsButtonTransition = transition
|
||||
if let variantsButtonView = self.variantsButton.view {
|
||||
if variantsButtonView.superview == nil && component.displayState == .default {
|
||||
varitantsButtonTransition = .immediate
|
||||
if let symbolDialView = self.symbolDial.view {
|
||||
self.insertSubview(variantsButtonView, aboveSubview: symbolDialView)
|
||||
} else {
|
||||
self.addSubview(variantsButtonView)
|
||||
var attributes: [ResaleGiftsContext.Attribute: StarGift.UniqueGift.Attribute] = [:]
|
||||
var backdropAttributeCount: [ResaleGiftsContext.Attribute: (Int32, Int)] = [:]
|
||||
var patternAttributeCount: [ResaleGiftsContext.Attribute: (Int32, Int)] = [:]
|
||||
for (index, gift) in selectedGifts {
|
||||
for attribute in gift.gift.attributes {
|
||||
switch attribute {
|
||||
case let .backdrop(_, id, _, _, _, _, _):
|
||||
let attributeId: ResaleGiftsContext.Attribute = .backdrop(id)
|
||||
attributes[attributeId] = attribute
|
||||
if let (minPosition, count) = backdropAttributeCount[attributeId] {
|
||||
backdropAttributeCount[attributeId] = (min(index, minPosition), count + 1)
|
||||
} else {
|
||||
backdropAttributeCount[attributeId] = (index, 1)
|
||||
}
|
||||
case let .pattern(_, file, _):
|
||||
let attributeId: ResaleGiftsContext.Attribute = .pattern(file.fileId.id)
|
||||
attributes[attributeId] = attribute
|
||||
if let (minPosition, count) = patternAttributeCount[attributeId] {
|
||||
patternAttributeCount[attributeId] = (min(index, minPosition), count + 1)
|
||||
} else {
|
||||
patternAttributeCount[attributeId] = (index, 1)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
varitantsButtonTransition.setFrame(view: variantsButtonView, frame: variantsButtonFrame)
|
||||
varitantsButtonTransition.setBlur(layer: variantsButtonView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
if component.displayState == .crafting {
|
||||
}
|
||||
var attributesCount: [ResaleGiftsContext.Attribute: (Int32, Int)] = [:]
|
||||
for (attributeId, value) in backdropAttributeCount {
|
||||
attributesCount[attributeId] = value
|
||||
}
|
||||
for (attributeId, value) in patternAttributeCount {
|
||||
attributesCount[attributeId] = value
|
||||
}
|
||||
|
||||
var backdropAttributes: [(ResaleGiftsContext.Attribute, Int)] = []
|
||||
for (attributeId, count) in backdropAttributeCount {
|
||||
backdropAttributes.append((attributeId, count.1))
|
||||
}
|
||||
backdropAttributes = backdropAttributes.sorted(by: { lhs, rhs in
|
||||
if lhs.1 != rhs.1 {
|
||||
return lhs.1 > rhs.1
|
||||
} else {
|
||||
return attributesCount[lhs.0]!.0 < attributesCount[rhs.0]!.0
|
||||
}
|
||||
})
|
||||
var patternAttributes: [(ResaleGiftsContext.Attribute, Int)] = []
|
||||
for (attributeId, count) in patternAttributeCount {
|
||||
patternAttributes.append((attributeId, count.1))
|
||||
}
|
||||
patternAttributes = patternAttributes.sorted(by: { lhs, rhs in
|
||||
if lhs.1 != rhs.1 {
|
||||
return lhs.1 > rhs.1
|
||||
} else {
|
||||
return attributesCount[lhs.0]!.0 < attributesCount[rhs.0]!.0
|
||||
}
|
||||
})
|
||||
|
||||
var combinedAttributes: [ResaleGiftsContext.Attribute] = []
|
||||
for (attributeId, _) in backdropAttributes {
|
||||
combinedAttributes.append(attributeId)
|
||||
}
|
||||
for (attributeId, _) in patternAttributes {
|
||||
combinedAttributes.append(attributeId)
|
||||
}
|
||||
|
||||
let appConfiguration = component.context.currentAppConfiguration.with { $0 }
|
||||
let giftCraftConfiguration = GiftCraftConfiguration.with(appConfiguration: appConfiguration)
|
||||
|
||||
var firstRowCount = 0
|
||||
var secondRowCount = 0
|
||||
switch combinedAttributes.count {
|
||||
case 0, 1, 2, 3, 4:
|
||||
firstRowCount = combinedAttributes.count
|
||||
case 5:
|
||||
firstRowCount = 2
|
||||
secondRowCount = 3
|
||||
case 6:
|
||||
firstRowCount = 3
|
||||
secondRowCount = 3
|
||||
case 7:
|
||||
firstRowCount = 3
|
||||
secondRowCount = 4
|
||||
case 8:
|
||||
firstRowCount = 4
|
||||
secondRowCount = 4
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let attributeDialSpacing: CGFloat = 18.0
|
||||
let attributeDialSize = CGSize(width: 48.0, height: 48.0)
|
||||
|
||||
let attributeFirstRowTotalWidth = CGFloat(firstRowCount) * attributeDialSize.width + CGFloat(firstRowCount - 1) * attributeDialSpacing
|
||||
let attributeSecondRowTotalWidth = CGFloat(secondRowCount) * attributeDialSize.width + CGFloat(secondRowCount - 1) * attributeDialSpacing
|
||||
var attributeDialFrame: CGRect = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - attributeFirstRowTotalWidth) / 2.0), y: craftContentHeight), size: attributeDialSize)
|
||||
|
||||
craftContentHeight += attributeDialSize.height
|
||||
craftContentHeight += 39.0
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
var attributeDialIndex = 0
|
||||
for attribute in combinedAttributes {
|
||||
let itemId = AnyHashable(attribute)
|
||||
validIds.append(itemId)
|
||||
|
||||
var itemTransition = transition
|
||||
let visibleItem: ComponentView<Empty>
|
||||
if let current = self.attributeDials[itemId] {
|
||||
visibleItem = current
|
||||
} else {
|
||||
visibleItem = ComponentView()
|
||||
self.attributeDials[itemId] = visibleItem
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
let tag: GenericComponentViewTag
|
||||
if let current = self.attributeDialTags[itemId] {
|
||||
tag = current
|
||||
} else {
|
||||
tag = GenericComponentViewTag()
|
||||
self.attributeDialTags[itemId] = tag
|
||||
}
|
||||
|
||||
let attributeCount = attributesCount[attribute]?.1 ?? 0
|
||||
let permille = Int(giftCraftConfiguration.craftAttributePermilles[selectedGifts.count - 1][attributeCount - 1])
|
||||
|
||||
let dialContent: AnyComponentWithIdentity<Empty>
|
||||
var dialContentSize: CGSize?
|
||||
let tooltipText: String
|
||||
switch attribute {
|
||||
case .backdrop:
|
||||
guard case let .backdrop(name, _, innerColor, outerColor, _, _, _) = attributes[attribute] else {
|
||||
continue
|
||||
}
|
||||
dialContent = AnyComponentWithIdentity(
|
||||
id: "color",
|
||||
component: AnyComponent(
|
||||
ColorSwatchComponent(
|
||||
innerColor: UIColor(rgb: UInt32(bitPattern: innerColor)),
|
||||
outerColor: UIColor(rgb: UInt32(bitPattern: outerColor))
|
||||
)
|
||||
)
|
||||
)
|
||||
tooltipText = environment.strings.Gift_Craft_BackdropTooltip("\(permille / 10)", name).string
|
||||
case .pattern:
|
||||
guard case let .pattern(name, file, _) = attributes[attribute] else {
|
||||
continue
|
||||
}
|
||||
dialContent = AnyComponentWithIdentity(
|
||||
id: "symbol",
|
||||
component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.ResourceContent(
|
||||
context: component.context,
|
||||
file: file,
|
||||
attemptSynchronously: true,
|
||||
providesPlaceholder: true
|
||||
),
|
||||
color: .white,
|
||||
size: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
)
|
||||
)
|
||||
dialContentSize = CGSize(width: 30.0, height: 30.0)
|
||||
tooltipText = environment.strings.Gift_Craft_SymbolTooltip("\(permille / 10)", name).string
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
let _ = visibleItem.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
DialIndicatorComponent(
|
||||
content: dialContent,
|
||||
backgroundColor: .white.withAlphaComponent(0.1),
|
||||
foregroundColor: .white,
|
||||
diameter: 48.0,
|
||||
contentSize: dialContentSize,
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
progress: CGFloat(permille) / 10.0 / 100.0,
|
||||
value: permille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
HapticFeedback().impact(.light)
|
||||
|
||||
#if DEBUG
|
||||
switch attribute {
|
||||
case .backdrop:
|
||||
self.component?.externalState.testFailOrSuccess = true
|
||||
case .pattern:
|
||||
self.component?.externalState.testFailOrSuccess = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
#endif
|
||||
self.showAttributeInfo(tag: tag, text: tooltipText)
|
||||
},
|
||||
tag: tag
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let itemView = visibleItem.view {
|
||||
if itemView.superview == nil {
|
||||
self.addSubview(itemView)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
itemTransition.setFrame(view: itemView, frame: attributeDialFrame)
|
||||
transition.setAlpha(view: itemView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: itemView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
attributeDialFrame.origin.x += attributeDialSize.width + attributeDialSpacing
|
||||
attributeDialIndex += 1
|
||||
|
||||
if attributeDialIndex == firstRowCount {
|
||||
attributeDialFrame.origin.x = floorToScreenPixels((availableSize.width - attributeSecondRowTotalWidth) / 2.0)
|
||||
attributeDialFrame.origin.y += 66.0
|
||||
}
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, item) in self.attributeDials {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
if let itemView = item.view {
|
||||
if !transition.animation.isImmediate {
|
||||
itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
itemView.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
itemView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.attributeDials.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
|
||||
if secondRowCount == 0, case .default = component.displayState {
|
||||
let variantsString = environment.strings.Gift_Craft_ViewVariants
|
||||
let variantsButtonMeasure = self.variantsButtonMeasure.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .clear)))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let variantsButton: ComponentView<Empty>
|
||||
if let current = self.variantsButton {
|
||||
variantsButton = current
|
||||
} else {
|
||||
variantsButton = ComponentView<Empty>()
|
||||
self.variantsButton = variantsButton
|
||||
}
|
||||
|
||||
let variantsButtonSize = CGSize(width: variantsButtonMeasure.width + 87.0, height: 24.0)
|
||||
if let gift = self.starGiftsMap[component.gift.giftId] {
|
||||
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
|
||||
|
||||
if let upgradePreview = self.upgradePreview {
|
||||
var i = 0
|
||||
for attribute in upgradePreview {
|
||||
if case .model = attribute {
|
||||
switch i {
|
||||
case 0:
|
||||
variant1 = .preview(attributes: [attribute], rarity: nil)
|
||||
case 1:
|
||||
variant2 = .preview(attributes: [attribute], rarity: nil)
|
||||
case 2:
|
||||
variant3 = .preview(attributes: [attribute], rarity: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = variantsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: variantsButtonSize,
|
||||
backgroundColor: component.colors.3,
|
||||
isDark: true,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "content", component: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: variant1,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: variant2,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: variant3,
|
||||
isPlaceholder: false,
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "text", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .white)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white)
|
||||
))
|
||||
], spacing: 3.0))),
|
||||
action: { [weak self] _ in
|
||||
HapticFeedback().impact(.light)
|
||||
|
||||
self?.openUpgradeVariants()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
}
|
||||
let variantsButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - variantsButtonSize.width) / 2.0), y: craftContentHeight), size: variantsButtonSize)
|
||||
var varitantsButtonTransition = transition
|
||||
if let variantsButtonView = variantsButton.view {
|
||||
if variantsButtonView.superview == nil {
|
||||
varitantsButtonTransition = .immediate
|
||||
if let descriptionView = self.descriptionText.view {
|
||||
self.insertSubview(variantsButtonView, aboveSubview: descriptionView)
|
||||
} else {
|
||||
self.addSubview(variantsButtonView)
|
||||
}
|
||||
}
|
||||
varitantsButtonTransition.setFrame(view: variantsButtonView, frame: variantsButtonFrame)
|
||||
}
|
||||
} else if let variantsButton = self.variantsButton {
|
||||
self.variantsButton = nil
|
||||
if let variantsButtonView = variantsButton.view {
|
||||
transition.setBlur(layer: variantsButtonView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
variantsButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
variantsButtonView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
craftContentHeight += 145.0
|
||||
|
||||
let permilleValue = selectedGifts.reduce(0, { $0 + Int($1.value.gift.craftChancePermille ?? 0) })
|
||||
if component.displayState == .crafting {
|
||||
//var craftingOriginY = craftContentHeight * 0.5 + 160.0
|
||||
var craftingOriginY = craftContentHeight * 0.5 - 16.0
|
||||
let offset = -(craftContentHeight - originalCraftContentHeight)
|
||||
let offset: CGFloat = 0.0
|
||||
|
||||
let craftingTitleSize = self.craftingTitle.update(
|
||||
transition: transition,
|
||||
|
|
@ -935,7 +1000,7 @@ private final class CraftGiftPageContent: Component {
|
|||
|
||||
if component.displayState == .failure {
|
||||
var failureOriginY = craftContentHeight * 0.5 - 16.0
|
||||
let offset = -(craftContentHeight - originalCraftContentHeight)
|
||||
let offset: CGFloat = 0.0
|
||||
|
||||
let failureTitleSize = self.failureTitle.update(
|
||||
transition: transition,
|
||||
|
|
@ -1081,6 +1146,9 @@ private final class CraftGiftPageContent: Component {
|
|||
guard let self, let component = self.component, let environment = self.environment, let genericGift = self.starGiftsMap[component.gift.giftId], let resaleContext = component.resaleContext() else {
|
||||
return
|
||||
}
|
||||
|
||||
HapticFeedback().impact(.light)
|
||||
|
||||
let selectController = SelectCraftGiftScreen(
|
||||
context: component.context,
|
||||
craftContext: component.craftContext,
|
||||
|
|
@ -1105,6 +1173,8 @@ private final class CraftGiftPageContent: Component {
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
HapticFeedback().impact(.light)
|
||||
|
||||
self.component?.removeGift(index)
|
||||
},
|
||||
willFinish: { [weak self] success in
|
||||
|
|
@ -1142,6 +1212,10 @@ private final class CraftGiftPageContent: Component {
|
|||
controller.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { _ in
|
||||
controller.dismiss()
|
||||
})
|
||||
|
||||
HapticFeedback().success()
|
||||
} else {
|
||||
HapticFeedback().error()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1482,7 +1556,7 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
permilleValue += gift.gift.craftChancePermille ?? 0
|
||||
}
|
||||
}
|
||||
if permilleValue > 900 {
|
||||
if permilleValue >= 950 {
|
||||
colors.0 = UIColor(rgb: 0x1b3b3d)
|
||||
colors.1 = UIColor(rgb: 0x1a2f38)
|
||||
colors.2 = UIColor(rgb: 0x22464a)
|
||||
|
|
@ -1689,6 +1763,8 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
state.displayInfo = false
|
||||
state.updated(transition: .spring(duration: 0.3))
|
||||
} else if state.displayFailure, let genericGift = externalState.starGiftsMap[component.gift.giftId] {
|
||||
HapticFeedback().impact(.light)
|
||||
|
||||
let selectController = SelectCraftGiftScreen(
|
||||
context: component.context,
|
||||
craftContext: component.craftContext,
|
||||
|
|
@ -1707,6 +1783,8 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
)
|
||||
environment.controller()?.push(selectController)
|
||||
} else {
|
||||
HapticFeedback().impact(.medium)
|
||||
|
||||
state.inProgress = true
|
||||
state.updated(transition: .spring(duration: 0.3))
|
||||
|
||||
|
|
@ -1876,23 +1954,25 @@ public class GiftCraftScreen: ViewControllerComponentContainer {
|
|||
private struct GiftCraftConfiguration {
|
||||
static var defaultValue: GiftCraftConfiguration {
|
||||
return GiftCraftConfiguration(
|
||||
craftAttributePermilles: [60, 180, 450, 1000]
|
||||
craftAttributePermilles: [[90], [80, 200], [70, 190, 460], [60, 180, 450, 1000]]
|
||||
)
|
||||
}
|
||||
|
||||
let craftAttributePermilles: [Int32]
|
||||
let craftAttributePermilles: [[Int32]]
|
||||
|
||||
fileprivate init(
|
||||
craftAttributePermilles: [Int32]
|
||||
craftAttributePermilles: [[Int32]]
|
||||
) {
|
||||
self.craftAttributePermilles = craftAttributePermilles
|
||||
}
|
||||
|
||||
static func with(appConfiguration: AppConfiguration) -> GiftCraftConfiguration {
|
||||
if let data = appConfiguration.data {
|
||||
var craftAttributePermilles: [Int32] = []
|
||||
if let value = data["stargifts_craft_attribute_permilles"] as? [Double] {
|
||||
craftAttributePermilles = value.map { Int32($0) }
|
||||
var craftAttributePermilles: [[Int32]] = []
|
||||
if let value = data["stargifts_craft_attribute_permilles"] as? [[Double]] {
|
||||
craftAttributePermilles = value.map { innerArray in
|
||||
innerArray.map { Int32($0) }
|
||||
}
|
||||
} else {
|
||||
craftAttributePermilles = GiftCraftConfiguration.defaultValue.craftAttributePermilles
|
||||
}
|
||||
|
|
@ -1905,20 +1985,3 @@ private struct GiftCraftConfiguration {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func animateRipple(parentView: UIView, screenCornerRadius: CGFloat, location: CGPoint) {
|
||||
if let snapshotView = parentView.snapshotView(afterScreenUpdates: false) {
|
||||
let wrappingNode = SpaceWarpNodeImpl()
|
||||
wrappingNode.isUserInteractionEnabled = false
|
||||
wrappingNode.frame = CGRect(origin: .zero, size: parentView.bounds.size)
|
||||
wrappingNode.update(size: parentView.bounds.size, cornerRadius: screenCornerRadius, transition: .immediate)
|
||||
parentView.addSubview(wrappingNode.view)
|
||||
wrappingNode.contentNode.view.addSubview(snapshotView)
|
||||
|
||||
wrappingNode.triggerRipple(at: location)
|
||||
|
||||
Queue.mainQueue().after(0.7, {
|
||||
wrappingNode.view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,8 @@ final class SelectGiftPageContent: Component {
|
|||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
HapticFeedback().impact(.light)
|
||||
|
||||
component.selectGift(gift)
|
||||
component.dismiss()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ public final class GiftItemComponent: Component {
|
|||
let animateChanges: Bool
|
||||
let mode: Mode
|
||||
let cornerRadius: CGFloat?
|
||||
let allowAnimations: Bool
|
||||
let action: (() -> Void)?
|
||||
let contextAction: ((UIView, ContextGesture) -> Void)?
|
||||
|
||||
|
|
@ -200,6 +201,7 @@ public final class GiftItemComponent: Component {
|
|||
animateChanges: Bool = false,
|
||||
mode: Mode = .generic,
|
||||
cornerRadius: CGFloat? = nil,
|
||||
allowAnimations: Bool = true,
|
||||
action: (() -> Void)? = nil,
|
||||
contextAction: ((UIView, ContextGesture) -> Void)? = nil
|
||||
) {
|
||||
|
|
@ -227,6 +229,7 @@ public final class GiftItemComponent: Component {
|
|||
self.animateChanges = animateChanges
|
||||
self.mode = mode
|
||||
self.cornerRadius = cornerRadius
|
||||
self.allowAnimations = allowAnimations
|
||||
self.action = action
|
||||
self.contextAction = contextAction
|
||||
}
|
||||
|
|
@ -304,6 +307,9 @@ public final class GiftItemComponent: Component {
|
|||
if lhs.cornerRadius != rhs.cornerRadius {
|
||||
return false
|
||||
}
|
||||
if lhs.allowAnimations != rhs.allowAnimations {
|
||||
return false
|
||||
}
|
||||
if (lhs.contextAction == nil) != (rhs.contextAction == nil) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -713,7 +719,7 @@ public final class GiftItemComponent: Component {
|
|||
pointSize: CGSize(width: iconSize.width * 2.0, height: iconSize.height * 2.0),
|
||||
loopCount: 1
|
||||
)
|
||||
animationLayer.isVisibleForAnimations = true
|
||||
animationLayer.isVisibleForAnimations = component.allowAnimations
|
||||
self.animationLayer = animationLayer
|
||||
|
||||
if let patternView = self.patternView.view {
|
||||
|
|
|
|||
|
|
@ -882,6 +882,8 @@ public final class GiftStoreContentComponent: Component {
|
|||
}
|
||||
let previousFilterAttributes = self.starGiftsState?.filterAttributes
|
||||
let previousSorting = self.starGiftsState?.sorting
|
||||
let previousDataState = self.starGiftsState?.dataState
|
||||
let previousItems = self.starGiftsState?.gifts
|
||||
self.starGiftsState = state
|
||||
|
||||
var transition: ComponentTransition = .immediate
|
||||
|
|
@ -890,6 +892,11 @@ public final class GiftStoreContentComponent: Component {
|
|||
} else if let previousSorting, previousSorting != state.sorting {
|
||||
transition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
|
||||
if previousItems?.isEmpty == true, case .loading = previousDataState, component.confirmPurchaseImmediately {
|
||||
transition = .spring(duration: 0.3)
|
||||
}
|
||||
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: transition)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -519,7 +519,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
title: title,
|
||||
ribbon: nil,
|
||||
isSelected: isSelected,
|
||||
mode: .upgradePreview
|
||||
mode: .upgradePreview,
|
||||
allowAnimations: self.selectedSection != .backdrops
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue