mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add support for stars withdrawal timeout
This commit is contained in:
parent
a232ba765f
commit
c0489e251b
@ -12323,6 +12323,7 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Story.ViewLink" = "Open Link";
|
"Story.ViewLink" = "Open Link";
|
||||||
|
|
||||||
|
"PeerInfo.Bot.Username" = "Username";
|
||||||
"PeerInfo.Bot.Balance" = "Balance";
|
"PeerInfo.Bot.Balance" = "Balance";
|
||||||
"PeerInfo.Bot.Balance.Stars_1" = "%@ Star";
|
"PeerInfo.Bot.Balance.Stars_1" = "%@ Star";
|
||||||
"PeerInfo.Bot.Balance.Stars_any" = "%@ Stars";
|
"PeerInfo.Bot.Balance.Stars_any" = "%@ Stars";
|
||||||
@ -12349,10 +12350,12 @@ Sorry for the inconvenience.";
|
|||||||
"Stars.Withdraw.AmountPlaceholder" = "Stars Amount";
|
"Stars.Withdraw.AmountPlaceholder" = "Stars Amount";
|
||||||
"Stars.Withdraw.Withdraw" = "Withdraw";
|
"Stars.Withdraw.Withdraw" = "Withdraw";
|
||||||
|
|
||||||
"Stars.Withdraw.Withdraw.ErrorMinimum" = "You cannot withdraw less than %@";
|
"Stars.Withdraw.Withdraw.ErrorMinimum" = "You cannot withdraw less than [%@]().";
|
||||||
"Stars.Withdraw.Withdraw.ErrorMinimum.Stars_1" = "%@ Star";
|
"Stars.Withdraw.Withdraw.ErrorMinimum.Stars_1" = "%@ Star";
|
||||||
"Stars.Withdraw.Withdraw.ErrorMinimum.Stars_any" = "%@ Stars";
|
"Stars.Withdraw.Withdraw.ErrorMinimum.Stars_any" = "%@ Stars";
|
||||||
|
|
||||||
|
"Stars.Withdraw.Withdraw.ErrorTimeout" = "Next withdrawal will be available in **%@**.";
|
||||||
|
|
||||||
"Stars.PaidContent.Title" = "Paid Content";
|
"Stars.PaidContent.Title" = "Paid Content";
|
||||||
"Stars.PaidContent.AmountTitle" = "ENTER UNLOCK COST";
|
"Stars.PaidContent.AmountTitle" = "ENTER UNLOCK COST";
|
||||||
"Stars.PaidContent.AmountPlaceholder" = "Stars to Unlock";
|
"Stars.PaidContent.AmountPlaceholder" = "Stars to Unlock";
|
||||||
@ -12369,3 +12372,7 @@ Sorry for the inconvenience.";
|
|||||||
"MediaEditor.Link.LinkName.Placeholder" = "Enter a Name";
|
"MediaEditor.Link.LinkName.Placeholder" = "Enter a Name";
|
||||||
|
|
||||||
"Story.Editor.TooltipLinkPremium" = "Subscribe to [Telegram Premium]() to add links.";
|
"Story.Editor.TooltipLinkPremium" = "Subscribe to [Telegram Premium]() to add links.";
|
||||||
|
|
||||||
|
"Story.Editor.TooltipLinkLimitValue_1" = "**%@** link";
|
||||||
|
"Story.Editor.TooltipLinkLimitValue_any" = "**%@** links";
|
||||||
|
"Story.Editor.TooltipReachedLinkLimitText" = "You can't add more than %@ to a story.";
|
||||||
|
@ -3088,6 +3088,7 @@ public final class DrawingToolsInteraction {
|
|||||||
var isVideo = false
|
var isVideo = false
|
||||||
var isAdditional = false
|
var isAdditional = false
|
||||||
var isMessage = false
|
var isMessage = false
|
||||||
|
var isLink = false
|
||||||
if let entity = entityView.entity as? DrawingStickerEntity {
|
if let entity = entityView.entity as? DrawingStickerEntity {
|
||||||
if case let .dualVideoReference(isAdditionalValue) = entity.content {
|
if case let .dualVideoReference(isAdditionalValue) = entity.content {
|
||||||
isVideo = true
|
isVideo = true
|
||||||
@ -3095,6 +3096,8 @@ public final class DrawingToolsInteraction {
|
|||||||
} else if case .message = entity.content {
|
} else if case .message = entity.content {
|
||||||
isMessage = true
|
isMessage = true
|
||||||
}
|
}
|
||||||
|
} else if entityView.entity is DrawingLinkEntity {
|
||||||
|
isLink = true
|
||||||
}
|
}
|
||||||
|
|
||||||
guard (!isVideo || isAdditional) && (!isMessage || !isTopmost) else {
|
guard (!isVideo || isAdditional) && (!isMessage || !isTopmost) else {
|
||||||
@ -3140,7 +3143,7 @@ public final class DrawingToolsInteraction {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if !isVideo && !isMessage {
|
if !isVideo && !isMessage && !isLink {
|
||||||
if let stickerEntity = entityView.entity as? DrawingStickerEntity, case let .file(_, type) = stickerEntity.content, case .reaction = type {
|
if let stickerEntity = entityView.entity as? DrawingStickerEntity, case let .file(_, type) = stickerEntity.content, case .reaction = type {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -518,7 +518,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) }
|
dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) }
|
||||||
dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) }
|
dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) }
|
||||||
dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
|
dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
|
||||||
dict[64088654] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
||||||
dict[-1808510398] = { return Api.Message.parse_message($0) }
|
dict[-1808510398] = { return Api.Message.parse_message($0) }
|
||||||
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
||||||
dict[721967202] = { return Api.Message.parse_messageService($0) }
|
dict[721967202] = { return Api.Message.parse_messageService($0) }
|
||||||
@ -872,7 +872,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) }
|
dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) }
|
||||||
dict[-1108478618] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
dict[-1108478618] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
||||||
dict[-407138204] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) }
|
dict[2033461574] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) }
|
||||||
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
|
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
|
||||||
dict[-1442789224] = { return Api.StarsTransaction.parse_starsTransaction($0) }
|
dict[-1442789224] = { return Api.StarsTransaction.parse_starsTransaction($0) }
|
||||||
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
|
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
|
||||||
|
@ -300,33 +300,35 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum MediaAreaCoordinates: TypeConstructorDescription {
|
enum MediaAreaCoordinates: TypeConstructorDescription {
|
||||||
case mediaAreaCoordinates(x: Double, y: Double, w: Double, h: Double, rotation: Double)
|
case mediaAreaCoordinates(flags: Int32, x: Double, y: Double, w: Double, h: Double, rotation: Double, radius: Double?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .mediaAreaCoordinates(let x, let y, let w, let h, let rotation):
|
case .mediaAreaCoordinates(let flags, let x, let y, let w, let h, let rotation, let radius):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(64088654)
|
buffer.appendInt32(-808853502)
|
||||||
}
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeDouble(x, buffer: buffer, boxed: false)
|
serializeDouble(x, buffer: buffer, boxed: false)
|
||||||
serializeDouble(y, buffer: buffer, boxed: false)
|
serializeDouble(y, buffer: buffer, boxed: false)
|
||||||
serializeDouble(w, buffer: buffer, boxed: false)
|
serializeDouble(w, buffer: buffer, boxed: false)
|
||||||
serializeDouble(h, buffer: buffer, boxed: false)
|
serializeDouble(h, buffer: buffer, boxed: false)
|
||||||
serializeDouble(rotation, buffer: buffer, boxed: false)
|
serializeDouble(rotation, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeDouble(radius!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .mediaAreaCoordinates(let x, let y, let w, let h, let rotation):
|
case .mediaAreaCoordinates(let flags, let x, let y, let w, let h, let rotation, let radius):
|
||||||
return ("mediaAreaCoordinates", [("x", x as Any), ("y", y as Any), ("w", w as Any), ("h", h as Any), ("rotation", rotation as Any)])
|
return ("mediaAreaCoordinates", [("flags", flags as Any), ("x", x as Any), ("y", y as Any), ("w", w as Any), ("h", h as Any), ("rotation", rotation as Any), ("radius", radius as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func parse_mediaAreaCoordinates(_ reader: BufferReader) -> MediaAreaCoordinates? {
|
public static func parse_mediaAreaCoordinates(_ reader: BufferReader) -> MediaAreaCoordinates? {
|
||||||
var _1: Double?
|
var _1: Int32?
|
||||||
_1 = reader.readDouble()
|
_1 = reader.readInt32()
|
||||||
var _2: Double?
|
var _2: Double?
|
||||||
_2 = reader.readDouble()
|
_2 = reader.readDouble()
|
||||||
var _3: Double?
|
var _3: Double?
|
||||||
@ -335,13 +337,19 @@ public extension Api {
|
|||||||
_4 = reader.readDouble()
|
_4 = reader.readDouble()
|
||||||
var _5: Double?
|
var _5: Double?
|
||||||
_5 = reader.readDouble()
|
_5 = reader.readDouble()
|
||||||
|
var _6: Double?
|
||||||
|
_6 = reader.readDouble()
|
||||||
|
var _7: Double?
|
||||||
|
if Int(_1!) & Int(1 << 0) != 0 {_7 = reader.readDouble() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
let _c4 = _4 != nil
|
let _c4 = _4 != nil
|
||||||
let _c5 = _5 != nil
|
let _c5 = _5 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
let _c6 = _6 != nil
|
||||||
return Api.MediaAreaCoordinates.mediaAreaCoordinates(x: _1!, y: _2!, w: _3!, h: _4!, rotation: _5!)
|
let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||||
|
return Api.MediaAreaCoordinates.mediaAreaCoordinates(flags: _1!, x: _2!, y: _3!, w: _4!, h: _5!, rotation: _6!, radius: _7)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -604,26 +604,27 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum StarsRevenueStatus: TypeConstructorDescription {
|
enum StarsRevenueStatus: TypeConstructorDescription {
|
||||||
case starsRevenueStatus(flags: Int32, currentBalance: Int64, availableBalance: Int64, overallRevenue: Int64)
|
case starsRevenueStatus(flags: Int32, currentBalance: Int64, availableBalance: Int64, overallRevenue: Int64, nextWithdrawalAt: Int32?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .starsRevenueStatus(let flags, let currentBalance, let availableBalance, let overallRevenue):
|
case .starsRevenueStatus(let flags, let currentBalance, let availableBalance, let overallRevenue, let nextWithdrawalAt):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-407138204)
|
buffer.appendInt32(2033461574)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt64(currentBalance, buffer: buffer, boxed: false)
|
serializeInt64(currentBalance, buffer: buffer, boxed: false)
|
||||||
serializeInt64(availableBalance, buffer: buffer, boxed: false)
|
serializeInt64(availableBalance, buffer: buffer, boxed: false)
|
||||||
serializeInt64(overallRevenue, buffer: buffer, boxed: false)
|
serializeInt64(overallRevenue, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(nextWithdrawalAt!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .starsRevenueStatus(let flags, let currentBalance, let availableBalance, let overallRevenue):
|
case .starsRevenueStatus(let flags, let currentBalance, let availableBalance, let overallRevenue, let nextWithdrawalAt):
|
||||||
return ("starsRevenueStatus", [("flags", flags as Any), ("currentBalance", currentBalance as Any), ("availableBalance", availableBalance as Any), ("overallRevenue", overallRevenue as Any)])
|
return ("starsRevenueStatus", [("flags", flags as Any), ("currentBalance", currentBalance as Any), ("availableBalance", availableBalance as Any), ("overallRevenue", overallRevenue as Any), ("nextWithdrawalAt", nextWithdrawalAt as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,12 +637,15 @@ public extension Api {
|
|||||||
_3 = reader.readInt64()
|
_3 = reader.readInt64()
|
||||||
var _4: Int64?
|
var _4: Int64?
|
||||||
_4 = reader.readInt64()
|
_4 = reader.readInt64()
|
||||||
|
var _5: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
let _c4 = _4 != nil
|
let _c4 = _4 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 {
|
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||||
return Api.StarsRevenueStatus.starsRevenueStatus(flags: _1!, currentBalance: _2!, availableBalance: _3!, overallRevenue: _4!)
|
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||||
|
return Api.StarsRevenueStatus.starsRevenueStatus(flags: _1!, currentBalance: _2!, availableBalance: _3!, overallRevenue: _4!, nextWithdrawalAt: _5)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -473,8 +473,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
|
func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
|
||||||
func coodinatesFromApiMediaAreaCoordinates(_ coordinates: Api.MediaAreaCoordinates) -> MediaArea.Coordinates {
|
func coodinatesFromApiMediaAreaCoordinates(_ coordinates: Api.MediaAreaCoordinates) -> MediaArea.Coordinates {
|
||||||
switch coordinates {
|
switch coordinates {
|
||||||
case let .mediaAreaCoordinates(x, y, width, height, rotation):
|
case let .mediaAreaCoordinates(_, x, y, width, height, rotation, radius):
|
||||||
return MediaArea.Coordinates(x: x, y: y, width: width, height: height, rotation: rotation)
|
return MediaArea.Coordinates(x: x, y: y, width: width, height: height, rotation: rotation, cornerRadius: radius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch mediaArea {
|
switch mediaArea {
|
||||||
@ -551,7 +551,11 @@ func apiMediaAreasFromMediaAreas(_ mediaAreas: [MediaArea], transaction: Transac
|
|||||||
var apiMediaAreas: [Api.MediaArea] = []
|
var apiMediaAreas: [Api.MediaArea] = []
|
||||||
for area in mediaAreas {
|
for area in mediaAreas {
|
||||||
let coordinates = area.coordinates
|
let coordinates = area.coordinates
|
||||||
let inputCoordinates = Api.MediaAreaCoordinates.mediaAreaCoordinates(x: coordinates.x, y: coordinates.y, w: coordinates.width, h: coordinates.height, rotation: coordinates.rotation)
|
var flags: Int32 = 0
|
||||||
|
if let _ = coordinates.cornerRadius {
|
||||||
|
flags |= (1 << 0)
|
||||||
|
}
|
||||||
|
let inputCoordinates = Api.MediaAreaCoordinates.mediaAreaCoordinates(flags: flags, x: coordinates.x, y: coordinates.y, w: coordinates.width, h: coordinates.height, rotation: coordinates.rotation, radius: coordinates.cornerRadius)
|
||||||
switch area {
|
switch area {
|
||||||
case let .venue(_, venue):
|
case let .venue(_, venue):
|
||||||
if let queryId = venue.queryId, let resultId = venue.resultId {
|
if let queryId = venue.queryId, let resultId = venue.resultId {
|
||||||
|
@ -10,6 +10,7 @@ public struct StarsRevenueStats: Equatable {
|
|||||||
public let availableBalance: Int64
|
public let availableBalance: Int64
|
||||||
public let overallRevenue: Int64
|
public let overallRevenue: Int64
|
||||||
public let withdrawEnabled: Bool
|
public let withdrawEnabled: Bool
|
||||||
|
public let nextWithdrawalTimestamp: Int32?
|
||||||
}
|
}
|
||||||
|
|
||||||
public let revenueGraph: StatsGraph
|
public let revenueGraph: StatsGraph
|
||||||
@ -58,8 +59,8 @@ extension StarsRevenueStats {
|
|||||||
extension StarsRevenueStats.Balances {
|
extension StarsRevenueStats.Balances {
|
||||||
init(apiStarsRevenueStatus: Api.StarsRevenueStatus) {
|
init(apiStarsRevenueStatus: Api.StarsRevenueStatus) {
|
||||||
switch apiStarsRevenueStatus {
|
switch apiStarsRevenueStatus {
|
||||||
case let .starsRevenueStatus(flags, currentBalance, availableBalance, overallRevenue):
|
case let .starsRevenueStatus(flags, currentBalance, availableBalance, overallRevenue, nextWithdrawalAt):
|
||||||
self.init(currentBalance: currentBalance, availableBalance: availableBalance, overallRevenue: overallRevenue, withdrawEnabled: ((flags & (1 << 0)) != 0))
|
self.init(currentBalance: currentBalance, availableBalance: availableBalance, overallRevenue: overallRevenue, withdrawEnabled: ((flags & (1 << 0)) != 0), nextWithdrawalTimestamp: nextWithdrawalAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ public enum MediaArea: Codable, Equatable {
|
|||||||
case width
|
case width
|
||||||
case height
|
case height
|
||||||
case rotation
|
case rotation
|
||||||
|
case cornerRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
public var x: Double
|
public var x: Double
|
||||||
@ -23,19 +24,22 @@ public enum MediaArea: Codable, Equatable {
|
|||||||
public var width: Double
|
public var width: Double
|
||||||
public var height: Double
|
public var height: Double
|
||||||
public var rotation: Double
|
public var rotation: Double
|
||||||
|
public var cornerRadius: Double?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
x: Double,
|
x: Double,
|
||||||
y: Double,
|
y: Double,
|
||||||
width: Double,
|
width: Double,
|
||||||
height: Double,
|
height: Double,
|
||||||
rotation: Double
|
rotation: Double,
|
||||||
|
cornerRadius: Double?
|
||||||
) {
|
) {
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
|
self.cornerRadius = cornerRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -46,6 +50,7 @@ public enum MediaArea: Codable, Equatable {
|
|||||||
self.width = try container.decode(Double.self, forKey: .width)
|
self.width = try container.decode(Double.self, forKey: .width)
|
||||||
self.height = try container.decode(Double.self, forKey: .height)
|
self.height = try container.decode(Double.self, forKey: .height)
|
||||||
self.rotation = try container.decode(Double.self, forKey: .rotation)
|
self.rotation = try container.decode(Double.self, forKey: .rotation)
|
||||||
|
self.cornerRadius = try container.decodeIfPresent(Double.self, forKey: .cornerRadius)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -56,6 +61,7 @@ public enum MediaArea: Codable, Equatable {
|
|||||||
try container.encode(self.width, forKey: .width)
|
try container.encode(self.width, forKey: .width)
|
||||||
try container.encode(self.height, forKey: .height)
|
try container.encode(self.height, forKey: .height)
|
||||||
try container.encode(self.rotation, forKey: .rotation)
|
try container.encode(self.rotation, forKey: .rotation)
|
||||||
|
try container.encodeIfPresent(self.cornerRadius, forKey: .cornerRadius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,8 @@ public enum CodableDrawingEntity: Equatable {
|
|||||||
y: position.y / 1920.0 * 100.0,
|
y: position.y / 1920.0 * 100.0,
|
||||||
width: size.width * scale / 1080.0 * 100.0,
|
width: size.width * scale / 1080.0 * 100.0,
|
||||||
height: size.height * scale / 1920.0 * 100.0,
|
height: size.height * scale / 1920.0 * 100.0,
|
||||||
rotation: rotation / .pi * 180.0
|
rotation: rotation / .pi * 180.0,
|
||||||
|
cornerRadius: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
placeholderColor: theme.list.itemPlaceholderTextColor,
|
placeholderColor: theme.list.itemPlaceholderTextColor,
|
||||||
text: state.name,
|
text: state.name,
|
||||||
link: false,
|
link: false,
|
||||||
placeholderText: strings.MediaEditor_Link_LinkTo_Placeholder,
|
placeholderText: strings.MediaEditor_Link_LinkName_Placeholder,
|
||||||
textUpdated: { [weak state] text in
|
textUpdated: { [weak state] text in
|
||||||
state?.name = text
|
state?.name = text
|
||||||
}
|
}
|
||||||
|
@ -4485,6 +4485,20 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if existingEntity == nil {
|
||||||
|
let maxLinkCount = 3
|
||||||
|
var currentLinkCount = 0
|
||||||
|
self.entitiesView.eachView { entityView in
|
||||||
|
if entityView.entity is DrawingLinkEntity {
|
||||||
|
currentLinkCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currentLinkCount >= maxLinkCount {
|
||||||
|
controller.presentLinkLimitTooltip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var link: CreateLinkScreen.Link?
|
var link: CreateLinkScreen.Link?
|
||||||
if let existingEntity {
|
if let existingEntity {
|
||||||
link = CreateLinkScreen.Link(
|
link = CreateLinkScreen.Link(
|
||||||
@ -5977,6 +5991,29 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self.present(controller, in: .current)
|
self.present(controller, in: .current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func presentLinkLimitTooltip() {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
|
let context = self.context
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let limit: Int32 = 3
|
||||||
|
|
||||||
|
let value = presentationData.strings.Story_Editor_TooltipLinkLimitValue(limit)
|
||||||
|
let content: UndoOverlayContent = .info(
|
||||||
|
title: nil,
|
||||||
|
text: presentationData.strings.Story_Editor_TooltipReachedLinkLimitText(value).string,
|
||||||
|
timeout: nil,
|
||||||
|
customUndoText: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
let controller = UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: true, position: .top, animateInAsReplacement: false, action: { _ in
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
self.present(controller, in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
func maybePresentDiscardAlert() {
|
func maybePresentDiscardAlert() {
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
if !self.isEligibleForDraft() {
|
if !self.isEligibleForDraft() {
|
||||||
|
@ -1729,8 +1729,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
let ItemBotInfo = 10
|
let ItemBotInfo = 10
|
||||||
|
|
||||||
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||||
//TODO:localize
|
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: {
|
||||||
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: "Username", icon: PresentationResourcesSettings.bot, action: {
|
|
||||||
interaction.editingOpenPublicLinkSetup()
|
interaction.editingOpenPublicLinkSetup()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -7,7 +7,9 @@ import AccountContext
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import SolidRoundedButtonComponent
|
import ButtonComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
final class StarsBalanceComponent: Component {
|
final class StarsBalanceComponent: Component {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
@ -17,6 +19,8 @@ final class StarsBalanceComponent: Component {
|
|||||||
let rate: Double?
|
let rate: Double?
|
||||||
let actionTitle: String
|
let actionTitle: String
|
||||||
let actionAvailable: Bool
|
let actionAvailable: Bool
|
||||||
|
let actionIsEnabled: Bool
|
||||||
|
let actionCooldownUntilTimestamp: Int32?
|
||||||
let buy: () -> Void
|
let buy: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -27,6 +31,8 @@ final class StarsBalanceComponent: Component {
|
|||||||
rate: Double?,
|
rate: Double?,
|
||||||
actionTitle: String,
|
actionTitle: String,
|
||||||
actionAvailable: Bool,
|
actionAvailable: Bool,
|
||||||
|
actionIsEnabled: Bool,
|
||||||
|
actionCooldownUntilTimestamp: Int32? = nil,
|
||||||
buy: @escaping () -> Void
|
buy: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -36,6 +42,8 @@ final class StarsBalanceComponent: Component {
|
|||||||
self.rate = rate
|
self.rate = rate
|
||||||
self.actionTitle = actionTitle
|
self.actionTitle = actionTitle
|
||||||
self.actionAvailable = actionAvailable
|
self.actionAvailable = actionAvailable
|
||||||
|
self.actionIsEnabled = actionIsEnabled
|
||||||
|
self.actionCooldownUntilTimestamp = actionCooldownUntilTimestamp
|
||||||
self.buy = buy
|
self.buy = buy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +63,12 @@ final class StarsBalanceComponent: Component {
|
|||||||
if lhs.actionAvailable != rhs.actionAvailable {
|
if lhs.actionAvailable != rhs.actionAvailable {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.actionIsEnabled != rhs.actionIsEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.actionCooldownUntilTimestamp != rhs.actionCooldownUntilTimestamp {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.count != rhs.count {
|
if lhs.count != rhs.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -71,6 +85,9 @@ final class StarsBalanceComponent: Component {
|
|||||||
private var button = ComponentView<Empty>()
|
private var button = ComponentView<Empty>()
|
||||||
|
|
||||||
private var component: StarsBalanceComponent?
|
private var component: StarsBalanceComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var timer: Timer?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -86,6 +103,29 @@ final class StarsBalanceComponent: Component {
|
|||||||
|
|
||||||
func update(component: StarsBalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: StarsBalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.component = component
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
var remainingCooldownSeconds: Int32 = 0
|
||||||
|
if let cooldownUntilTimestamp = component.actionCooldownUntilTimestamp {
|
||||||
|
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||||
|
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remainingCooldownSeconds > 0 {
|
||||||
|
if self.timer == nil {
|
||||||
|
self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let timer = self.timer {
|
||||||
|
self.timer = nil
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
var contentHeight: CGFloat = sideInset
|
var contentHeight: CGFloat = sideInset
|
||||||
@ -147,19 +187,40 @@ final class StarsBalanceComponent: Component {
|
|||||||
if component.actionAvailable {
|
if component.actionAvailable {
|
||||||
contentHeight += 12.0
|
contentHeight += 12.0
|
||||||
|
|
||||||
|
let content: AnyComponentWithIdentity<Empty>
|
||||||
|
if remainingCooldownSeconds > 0 {
|
||||||
|
content = AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(
|
||||||
|
VStack([
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))),
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(HStack([
|
||||||
|
AnyComponentWithIdentity(id: 1, component: AnyComponent(BundleIconComponent(name: "Chat List/StatusLockIcon", tintColor: component.theme.list.itemCheckColors.fillColor.mixedWith(component.theme.list.itemCheckColors.foregroundColor, alpha: 0.7)))),
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(Text(text: stringForRemainingTime(remainingCooldownSeconds), font: Font.with(size: 11.0, weight: .medium, traits: [.monospacedNumbers]), color: component.theme.list.itemCheckColors.fillColor.mixedWith(component.theme.list.itemCheckColors.foregroundColor, alpha: 0.7))))
|
||||||
|
], spacing: 3.0)))
|
||||||
|
], spacing: 1.0)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor)))
|
||||||
|
}
|
||||||
|
|
||||||
let buttonSize = self.button.update(
|
let buttonSize = self.button.update(
|
||||||
transition: .immediate,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(ButtonComponent(
|
||||||
SolidRoundedButtonComponent(
|
background: ButtonComponent.Background(
|
||||||
title: component.actionTitle,
|
color: component.theme.list.itemCheckColors.fillColor,
|
||||||
theme: SolidRoundedButtonComponent.Theme(theme: component.theme),
|
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||||
height: 50.0,
|
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||||
cornerRadius: 11.0,
|
),
|
||||||
action: { [weak self] in
|
content: content,
|
||||||
self?.component?.buy()
|
isEnabled: component.actionIsEnabled,
|
||||||
|
allowActionWhenDisabled: false,
|
||||||
|
displaysProgress: false,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
)
|
component.buy()
|
||||||
),
|
}
|
||||||
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||||
)
|
)
|
||||||
@ -186,3 +247,16 @@ final class StarsBalanceComponent: Component {
|
|||||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringForRemainingTime(_ duration: Int32) -> String {
|
||||||
|
let hours = duration / 3600
|
||||||
|
let minutes = duration / 60 % 60
|
||||||
|
let seconds = duration % 60
|
||||||
|
let durationString: String
|
||||||
|
if hours > 0 {
|
||||||
|
durationString = String(format: "%d:%02d", hours, minutes)
|
||||||
|
} else {
|
||||||
|
durationString = String(format: "%02d:%02d", minutes, seconds)
|
||||||
|
}
|
||||||
|
return durationString
|
||||||
|
}
|
||||||
|
@ -7,7 +7,6 @@ import AccountContext
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import SolidRoundedButtonComponent
|
|
||||||
|
|
||||||
final class StarsOverviewItemComponent: Component {
|
final class StarsOverviewItemComponent: Component {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
@ -29,19 +29,22 @@ final class StarsStatisticsScreenComponent: Component {
|
|||||||
let revenueContext: StarsRevenueStatsContext
|
let revenueContext: StarsRevenueStatsContext
|
||||||
let openTransaction: (StarsContext.State.Transaction) -> Void
|
let openTransaction: (StarsContext.State.Transaction) -> Void
|
||||||
let buy: () -> Void
|
let buy: () -> Void
|
||||||
|
let showTimeoutTooltip: (Int32) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
revenueContext: StarsRevenueStatsContext,
|
revenueContext: StarsRevenueStatsContext,
|
||||||
openTransaction: @escaping (StarsContext.State.Transaction) -> Void,
|
openTransaction: @escaping (StarsContext.State.Transaction) -> Void,
|
||||||
buy: @escaping () -> Void
|
buy: @escaping () -> Void,
|
||||||
|
showTimeoutTooltip: @escaping (Int32) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.revenueContext = revenueContext
|
self.revenueContext = revenueContext
|
||||||
self.openTransaction = openTransaction
|
self.openTransaction = openTransaction
|
||||||
self.buy = buy
|
self.buy = buy
|
||||||
|
self.showTimeoutTooltip = showTimeoutTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StarsStatisticsScreenComponent, rhs: StarsStatisticsScreenComponent) -> Bool {
|
static func ==(lhs: StarsStatisticsScreenComponent, rhs: StarsStatisticsScreenComponent) -> Bool {
|
||||||
@ -459,11 +462,25 @@ final class StarsStatisticsScreenComponent: Component {
|
|||||||
rate: 0.2,
|
rate: 0.2,
|
||||||
actionTitle: strings.Stars_BotRevenue_Withdraw_Withdraw,
|
actionTitle: strings.Stars_BotRevenue_Withdraw_Withdraw,
|
||||||
actionAvailable: true,
|
actionAvailable: true,
|
||||||
|
actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true,
|
||||||
|
actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp,
|
||||||
buy: { [weak self] in
|
buy: { [weak self] in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.buy()
|
var remainingCooldownSeconds: Int32 = 0
|
||||||
|
if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp {
|
||||||
|
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||||
|
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
||||||
|
|
||||||
|
if remainingCooldownSeconds > 0 {
|
||||||
|
component.showTimeoutTooltip(cooldownUntilTimestamp)
|
||||||
|
} else {
|
||||||
|
component.buy()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
component.buy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
@ -597,13 +614,17 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: EnginePeer.Id
|
private let peerId: EnginePeer.Id
|
||||||
private let revenueContext: StarsRevenueStatsContext
|
private let revenueContext: StarsRevenueStatsContext
|
||||||
|
|
||||||
|
private weak var tooltipScreen: UndoOverlayController?
|
||||||
|
private var timer: Foundation.Timer?
|
||||||
|
|
||||||
public init(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) {
|
public init(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.revenueContext = revenueContext
|
self.revenueContext = revenueContext
|
||||||
|
|
||||||
var withdrawImpl: (() -> Void)?
|
var withdrawImpl: (() -> Void)?
|
||||||
|
var showTimeoutTooltipImpl: ((Int32) -> Void)?
|
||||||
var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)?
|
var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)?
|
||||||
super.init(context: context, component: StarsStatisticsScreenComponent(
|
super.init(context: context, component: StarsStatisticsScreenComponent(
|
||||||
context: context,
|
context: context,
|
||||||
@ -614,6 +635,9 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
|
|||||||
},
|
},
|
||||||
buy: {
|
buy: {
|
||||||
withdrawImpl?()
|
withdrawImpl?()
|
||||||
|
},
|
||||||
|
showTimeoutTooltip: { timestamp in
|
||||||
|
showTimeoutTooltipImpl?(timestamp)
|
||||||
}
|
}
|
||||||
), navigationBarAppearance: .transparent)
|
), navigationBarAppearance: .transparent)
|
||||||
|
|
||||||
@ -654,6 +678,10 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
|
|||||||
}, completion: { url in
|
}, completion: { url in
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
||||||
|
|
||||||
|
Queue.mainQueue().after(2.0) {
|
||||||
|
revenueContext.reload()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
self.present(controller, in: .window(.root))
|
self.present(controller, in: .window(.root))
|
||||||
})
|
})
|
||||||
@ -669,6 +697,59 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showTimeoutTooltipImpl = { [weak self] cooldownUntilTimestamp in
|
||||||
|
guard let self, self.tooltipScreen == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||||
|
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let content: UndoOverlayContent = .universal(
|
||||||
|
animation: "anim_clock",
|
||||||
|
scale: 0.058,
|
||||||
|
colors: [:],
|
||||||
|
title: nil,
|
||||||
|
text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorTimeout(stringForRemainingTime(remainingCooldownSeconds)).string,
|
||||||
|
customUndoText: nil,
|
||||||
|
timeout: nil
|
||||||
|
)
|
||||||
|
let controller = UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
self.tooltipScreen = controller
|
||||||
|
self.present(controller, in: .window(.root))
|
||||||
|
|
||||||
|
if remainingCooldownSeconds < 3600 {
|
||||||
|
if self.timer == nil {
|
||||||
|
self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let tooltipScreen = self.tooltipScreen {
|
||||||
|
let remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||||
|
let content: UndoOverlayContent = .universal(
|
||||||
|
animation: "anim_clock",
|
||||||
|
scale: 0.058,
|
||||||
|
colors: [:],
|
||||||
|
title: nil,
|
||||||
|
text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorTimeout(stringForRemainingTime(remainingCooldownSeconds)).string,
|
||||||
|
customUndoText: nil,
|
||||||
|
timeout: nil
|
||||||
|
)
|
||||||
|
tooltipScreen.content = content
|
||||||
|
} else {
|
||||||
|
if let timer = self.timer {
|
||||||
|
self.timer = nil
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
@ -524,6 +524,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
rate: nil,
|
rate: nil,
|
||||||
actionTitle: environment.strings.Stars_Intro_Buy,
|
actionTitle: environment.strings.Stars_Intro_Buy,
|
||||||
actionAvailable: !premiumConfiguration.areStarsDisabled,
|
actionAvailable: !premiumConfiguration.areStarsDisabled,
|
||||||
|
actionIsEnabled: true,
|
||||||
buy: { [weak self] in
|
buy: { [weak self] in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
|
@ -20,6 +20,7 @@ import AccountContext
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import ListSectionComponent
|
import ListSectionComponent
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
private let amountTag = GenericComponentViewTag()
|
private let amountTag = GenericComponentViewTag()
|
||||||
|
|
||||||
@ -286,8 +287,12 @@ private final class SheetContent: CombinedComponent {
|
|||||||
displaysProgress: false,
|
displaysProgress: false,
|
||||||
action: { [weak state] in
|
action: { [weak state] in
|
||||||
if let controller = controller() as? StarsWithdrawScreen, let amount = state?.amount {
|
if let controller = controller() as? StarsWithdrawScreen, let amount = state?.amount {
|
||||||
controller.completion(amount)
|
if let minAmount, amount < minAmount {
|
||||||
controller.dismissAnimated()
|
controller.presentMinAmountTooltip(minAmount)
|
||||||
|
} else {
|
||||||
|
controller.completion(amount)
|
||||||
|
controller.dismissAnimated()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -300,8 +305,8 @@ private final class SheetContent: CombinedComponent {
|
|||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
|
||||||
)
|
)
|
||||||
contentSize.height += button.size.height
|
contentSize.height += button.size.height
|
||||||
contentSize.height += 30.0
|
contentSize.height += 15.0
|
||||||
|
|
||||||
contentSize.height += max(environment.inputHeight, environment.safeInsets.bottom)
|
contentSize.height += max(environment.inputHeight, environment.safeInsets.bottom)
|
||||||
|
|
||||||
return contentSize
|
return contentSize
|
||||||
@ -469,6 +474,26 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presentMinAmountTooltip(_ minAmount: Int64) {
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let resultController = UndoOverlayController(
|
||||||
|
presentationData: presentationData,
|
||||||
|
content: .image(
|
||||||
|
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
||||||
|
title: nil,
|
||||||
|
text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string,
|
||||||
|
round: false,
|
||||||
|
undoText: nil
|
||||||
|
),
|
||||||
|
elevatedLayout: false,
|
||||||
|
action: { _ in return true})
|
||||||
|
self.present(resultController, in: .window(.root))
|
||||||
|
|
||||||
|
if let view = self.node.hostView.findTaggedView(tag: amountTag) as? AmountFieldComponent.View {
|
||||||
|
view.animateError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func dismissAnimated() {
|
public func dismissAnimated() {
|
||||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||||
@ -593,14 +618,7 @@ private final class AmountFieldComponent: Component {
|
|||||||
|
|
||||||
if let amount, let maxAmount = component.maxValue, amount > maxAmount {
|
if let amount, let maxAmount = component.maxValue, amount > maxAmount {
|
||||||
textField.text = "\(maxAmount)"
|
textField.text = "\(maxAmount)"
|
||||||
|
self.animateError()
|
||||||
textField.layer.addShakeAnimation()
|
|
||||||
let hapticFeedback = HapticFeedback()
|
|
||||||
hapticFeedback.error()
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
|
|
||||||
let _ = hapticFeedback
|
|
||||||
})
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -615,6 +633,15 @@ private final class AmountFieldComponent: Component {
|
|||||||
self.textField.selectAll(nil)
|
self.textField.selectAll(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateError() {
|
||||||
|
self.textField.layer.addShakeAnimation()
|
||||||
|
let hapticFeedback = HapticFeedback()
|
||||||
|
hapticFeedback.error()
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
|
||||||
|
let _ = hapticFeedback
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
self.textField.textColor = component.textColor
|
self.textField.textColor = component.textColor
|
||||||
if let value = component.value {
|
if let value = component.value {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1108,7 +1108,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||||
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
|
||||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
|
||||||
return ("URL", contents)
|
return ("URL", contents)
|
||||||
}), textAlignment: .natural)
|
}), textAlignment: .natural)
|
||||||
@ -1417,32 +1417,41 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
func updateContent(_ content: UndoOverlayContent) {
|
func updateContent(_ content: UndoOverlayContent) {
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
||||||
|
var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
|
||||||
|
|
||||||
switch content {
|
switch content {
|
||||||
case let .image(image, title, text, _, _):
|
case let .info(title, text, _, _), let .universal(_, _, _, title, text, _, _):
|
||||||
self.iconNode?.image = image
|
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||||
if let title = title {
|
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
||||||
} else {
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||||
self.titleNode.attributedText = nil
|
if let title {
|
||||||
}
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
}
|
||||||
self.renewWithCurrentContent()
|
self.textNode.attributedText = attributedText
|
||||||
case let .actionSucceeded(title, text, _, destructive):
|
case let .image(image, title, text, _, _):
|
||||||
var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
|
self.iconNode?.image = image
|
||||||
if destructive {
|
if let title = title {
|
||||||
undoTextColor = UIColor(rgb: 0xff7b74)
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
}
|
} else {
|
||||||
|
self.titleNode.attributedText = nil
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
}
|
||||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||||
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
self.renewWithCurrentContent()
|
||||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
case let .actionSucceeded(title, text, _, destructive):
|
||||||
if let title {
|
if destructive {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
undoTextColor = UIColor(rgb: 0xff7b74)
|
||||||
}
|
}
|
||||||
self.textNode.attributedText = attributedText
|
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||||
default:
|
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||||
break
|
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
||||||
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||||
|
if let title {
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
|
}
|
||||||
|
self.textNode.attributedText = attributedText
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if let validLayout = self.validLayout {
|
if let validLayout = self.validLayout {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user