mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-07 06:43:43 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
bc1598bb92
6
MODULE.bazel.lock
generated
6
MODULE.bazel.lock
generated
@ -11,7 +11,6 @@
|
|||||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
|
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
|
||||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
|
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
|
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101",
|
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
|
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
|
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
|
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
|
||||||
@ -24,6 +23,7 @@
|
|||||||
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
|
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
|
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
|
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
|
||||||
|
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
|
||||||
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
|
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
|
||||||
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
|
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
|
||||||
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
|
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
|
||||||
@ -144,8 +144,8 @@
|
|||||||
"https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
|
"https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
|
||||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
|
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
|
||||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
|
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
|
||||||
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f",
|
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
|
||||||
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b",
|
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
|
||||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
|
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
|
||||||
|
|||||||
@ -1306,6 +1306,10 @@ public struct ComponentTransition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func animateBlur(layer: CALayer, fromRadius: CGFloat, toRadius: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
public func animateBlur(layer: CALayer, fromRadius: CGFloat, toRadius: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||||
|
if case .none = self.animation {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if let blurFilter = CALayer.blur() {
|
if let blurFilter = CALayer.blur() {
|
||||||
blurFilter.setValue(toRadius as NSNumber, forKey: "inputRadius")
|
blurFilter.setValue(toRadius as NSNumber, forKey: "inputRadius")
|
||||||
layer.filters = [blurFilter]
|
layer.filters = [blurFilter]
|
||||||
|
|||||||
@ -303,6 +303,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
|
dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
|
||||||
dict[-674602536] = { return Api.GroupCall.parse_groupCall($0) }
|
dict[-674602536] = { return Api.GroupCall.parse_groupCall($0) }
|
||||||
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
|
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
|
||||||
|
dict[-297595771] = { return Api.GroupCallDonor.parse_groupCallDonor($0) }
|
||||||
dict[445316222] = { return Api.GroupCallMessage.parse_groupCallMessage($0) }
|
dict[445316222] = { return Api.GroupCallMessage.parse_groupCallMessage($0) }
|
||||||
dict[708691884] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
|
dict[708691884] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
|
||||||
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
|
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
|
||||||
@ -1485,6 +1486,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||||
dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) }
|
dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) }
|
||||||
dict[-1636664659] = { return Api.phone.GroupCall.parse_groupCall($0) }
|
dict[-1636664659] = { return Api.phone.GroupCall.parse_groupCall($0) }
|
||||||
|
dict[-1658995418] = { return Api.phone.GroupCallStars.parse_groupCallStars($0) }
|
||||||
dict[-790330702] = { return Api.phone.GroupCallStreamChannels.parse_groupCallStreamChannels($0) }
|
dict[-790330702] = { return Api.phone.GroupCallStreamChannels.parse_groupCallStreamChannels($0) }
|
||||||
dict[767505458] = { return Api.phone.GroupCallStreamRtmpUrl.parse_groupCallStreamRtmpUrl($0) }
|
dict[767505458] = { return Api.phone.GroupCallStreamRtmpUrl.parse_groupCallStreamRtmpUrl($0) }
|
||||||
dict[-193506890] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) }
|
dict[-193506890] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) }
|
||||||
@ -1820,6 +1822,8 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.GroupCall:
|
case let _1 as Api.GroupCall:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.GroupCallDonor:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.GroupCallMessage:
|
case let _1 as Api.GroupCallMessage:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.GroupCallParticipant:
|
case let _1 as Api.GroupCallParticipant:
|
||||||
@ -2640,6 +2644,8 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.phone.GroupCall:
|
case let _1 as Api.phone.GroupCall:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.phone.GroupCallStars:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.phone.GroupCallStreamChannels:
|
case let _1 as Api.phone.GroupCallStreamChannels:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.phone.GroupCallStreamRtmpUrl:
|
case let _1 as Api.phone.GroupCallStreamRtmpUrl:
|
||||||
|
|||||||
@ -1094,6 +1094,72 @@ public extension Api.phone {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.phone {
|
||||||
|
enum GroupCallStars: TypeConstructorDescription {
|
||||||
|
case groupCallStars(totalStars: Int64, topDonors: [Api.GroupCallDonor], chats: [Api.Chat], users: [Api.User])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .groupCallStars(let totalStars, let topDonors, let chats, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1658995418)
|
||||||
|
}
|
||||||
|
serializeInt64(totalStars, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(topDonors.count))
|
||||||
|
for item in topDonors {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(chats.count))
|
||||||
|
for item in chats {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users.count))
|
||||||
|
for item in users {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .groupCallStars(let totalStars, let topDonors, let chats, let users):
|
||||||
|
return ("groupCallStars", [("totalStars", totalStars as Any), ("topDonors", topDonors as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_groupCallStars(_ reader: BufferReader) -> GroupCallStars? {
|
||||||
|
var _1: Int64?
|
||||||
|
_1 = reader.readInt64()
|
||||||
|
var _2: [Api.GroupCallDonor]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallDonor.self)
|
||||||
|
}
|
||||||
|
var _3: [Api.Chat]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
|
}
|
||||||
|
var _4: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
let _c4 = _4 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 {
|
||||||
|
return Api.phone.GroupCallStars.groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.phone {
|
public extension Api.phone {
|
||||||
enum GroupCallStreamChannels: TypeConstructorDescription {
|
enum GroupCallStreamChannels: TypeConstructorDescription {
|
||||||
case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel])
|
case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel])
|
||||||
@ -1650,65 +1716,3 @@ public extension Api.premium {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.premium {
|
|
||||||
enum MyBoosts: TypeConstructorDescription {
|
|
||||||
case myBoosts(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User])
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .myBoosts(let myBoosts, let chats, let users):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(-1696454430)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(myBoosts.count))
|
|
||||||
for item in myBoosts {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(chats.count))
|
|
||||||
for item in chats {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(users.count))
|
|
||||||
for item in users {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .myBoosts(let myBoosts, let chats, let users):
|
|
||||||
return ("myBoosts", [("myBoosts", myBoosts as Any), ("chats", chats as Any), ("users", users as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_myBoosts(_ reader: BufferReader) -> MyBoosts? {
|
|
||||||
var _1: [Api.MyBoost]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MyBoost.self)
|
|
||||||
}
|
|
||||||
var _2: [Api.Chat]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
|
||||||
}
|
|
||||||
var _3: [Api.User]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
|
||||||
}
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = _2 != nil
|
|
||||||
let _c3 = _3 != nil
|
|
||||||
if _c1 && _c2 && _c3 {
|
|
||||||
return Api.premium.MyBoosts.myBoosts(myBoosts: _1!, chats: _2!, users: _3!)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,65 @@
|
|||||||
|
public extension Api.premium {
|
||||||
|
enum MyBoosts: TypeConstructorDescription {
|
||||||
|
case myBoosts(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .myBoosts(let myBoosts, let chats, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1696454430)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(myBoosts.count))
|
||||||
|
for item in myBoosts {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(chats.count))
|
||||||
|
for item in chats {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users.count))
|
||||||
|
for item in users {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .myBoosts(let myBoosts, let chats, let users):
|
||||||
|
return ("myBoosts", [("myBoosts", myBoosts as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_myBoosts(_ reader: BufferReader) -> MyBoosts? {
|
||||||
|
var _1: [Api.MyBoost]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MyBoost.self)
|
||||||
|
}
|
||||||
|
var _2: [Api.Chat]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
|
}
|
||||||
|
var _3: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.premium.MyBoosts.myBoosts(myBoosts: _1!, chats: _2!, users: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.smsjobs {
|
public extension Api.smsjobs {
|
||||||
enum EligibilityToJoin: TypeConstructorDescription {
|
enum EligibilityToJoin: TypeConstructorDescription {
|
||||||
case eligibleToJoin(termsUrl: String, monthlySentSms: Int32)
|
case eligibleToJoin(termsUrl: String, monthlySentSms: Int32)
|
||||||
|
|||||||
@ -10593,6 +10593,21 @@ public extension Api.functions.phone {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.phone {
|
||||||
|
static func getGroupCallStars(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStars>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1868784386)
|
||||||
|
call.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "phone.getGroupCallStars", parameters: [("call", String(describing: call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStars? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.phone.GroupCallStars?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStars
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.phone {
|
public extension Api.functions.phone {
|
||||||
static func getGroupCallStreamChannels(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStreamChannels>) {
|
static func getGroupCallStreamChannels(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStreamChannels>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
|||||||
@ -1332,6 +1332,52 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum GroupCallDonor: TypeConstructorDescription {
|
||||||
|
case groupCallDonor(flags: Int32, peerId: Api.Peer?, stars: Int64)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .groupCallDonor(let flags, let peerId, let stars):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-297595771)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {peerId!.serialize(buffer, true)}
|
||||||
|
serializeInt64(stars, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .groupCallDonor(let flags, let peerId, let stars):
|
||||||
|
return ("groupCallDonor", [("flags", flags as Any), ("peerId", peerId as Any), ("stars", stars as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_groupCallDonor(_ reader: BufferReader) -> GroupCallDonor? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Api.Peer?
|
||||||
|
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
|
} }
|
||||||
|
var _3: Int64?
|
||||||
|
_3 = reader.readInt64()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.GroupCallDonor.groupCallDonor(flags: _1!, peerId: _2, stars: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum GroupCallMessage: TypeConstructorDescription {
|
enum GroupCallMessage: TypeConstructorDescription {
|
||||||
case groupCallMessage(flags: Int32, id: Int32, fromId: Api.Peer, date: Int32, message: Api.TextWithEntities, paidMessageStars: Int64?)
|
case groupCallMessage(flags: Int32, id: Int32, fromId: Api.Peer, date: Int32, message: Api.TextWithEntities, paidMessageStars: Int64?)
|
||||||
@ -1392,85 +1438,3 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api {
|
|
||||||
enum GroupCallParticipant: TypeConstructorDescription {
|
|
||||||
case groupCallParticipant(flags: Int32, peer: Api.Peer, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, about: String?, raiseHandRating: Int64?, video: Api.GroupCallParticipantVideo?, presentation: Api.GroupCallParticipantVideo?, paidStarsTotal: Int64?)
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation, let paidStarsTotal):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(708691884)
|
|
||||||
}
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
serializeInt32(date, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(activeDate!, buffer: buffer, boxed: false)}
|
|
||||||
serializeInt32(source, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
|
|
||||||
if Int(flags) & Int(1 << 11) != 0 {serializeString(about!, buffer: buffer, boxed: false)}
|
|
||||||
if Int(flags) & Int(1 << 13) != 0 {serializeInt64(raiseHandRating!, buffer: buffer, boxed: false)}
|
|
||||||
if Int(flags) & Int(1 << 6) != 0 {video!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 14) != 0 {presentation!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 16) != 0 {serializeInt64(paidStarsTotal!, buffer: buffer, boxed: false)}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation, let paidStarsTotal):
|
|
||||||
return ("groupCallParticipant", [("flags", flags as Any), ("peer", peer as Any), ("date", date as Any), ("activeDate", activeDate as Any), ("source", source as Any), ("volume", volume as Any), ("about", about as Any), ("raiseHandRating", raiseHandRating as Any), ("video", video as Any), ("presentation", presentation as Any), ("paidStarsTotal", paidStarsTotal as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_groupCallParticipant(_ reader: BufferReader) -> GroupCallParticipant? {
|
|
||||||
var _1: Int32?
|
|
||||||
_1 = reader.readInt32()
|
|
||||||
var _2: Api.Peer?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
_2 = Api.parse(reader, signature: signature) as? Api.Peer
|
|
||||||
}
|
|
||||||
var _3: Int32?
|
|
||||||
_3 = reader.readInt32()
|
|
||||||
var _4: Int32?
|
|
||||||
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
|
|
||||||
var _5: Int32?
|
|
||||||
_5 = reader.readInt32()
|
|
||||||
var _6: Int32?
|
|
||||||
if Int(_1!) & Int(1 << 7) != 0 {_6 = reader.readInt32() }
|
|
||||||
var _7: String?
|
|
||||||
if Int(_1!) & Int(1 << 11) != 0 {_7 = parseString(reader) }
|
|
||||||
var _8: Int64?
|
|
||||||
if Int(_1!) & Int(1 << 13) != 0 {_8 = reader.readInt64() }
|
|
||||||
var _9: Api.GroupCallParticipantVideo?
|
|
||||||
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
|
|
||||||
_9 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo
|
|
||||||
} }
|
|
||||||
var _10: Api.GroupCallParticipantVideo?
|
|
||||||
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
|
|
||||||
_10 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo
|
|
||||||
} }
|
|
||||||
var _11: Int64?
|
|
||||||
if Int(_1!) & Int(1 << 16) != 0 {_11 = reader.readInt64() }
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = _2 != nil
|
|
||||||
let _c3 = _3 != nil
|
|
||||||
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
|
|
||||||
let _c5 = _5 != nil
|
|
||||||
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
|
|
||||||
let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil
|
|
||||||
let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil
|
|
||||||
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
|
|
||||||
let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil
|
|
||||||
let _c11 = (Int(_1!) & Int(1 << 16) == 0) || _11 != nil
|
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
|
||||||
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10, paidStarsTotal: _11)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,85 @@
|
|||||||
|
public extension Api {
|
||||||
|
enum GroupCallParticipant: TypeConstructorDescription {
|
||||||
|
case groupCallParticipant(flags: Int32, peer: Api.Peer, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, about: String?, raiseHandRating: Int64?, video: Api.GroupCallParticipantVideo?, presentation: Api.GroupCallParticipantVideo?, paidStarsTotal: Int64?)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation, let paidStarsTotal):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(708691884)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(date, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(activeDate!, buffer: buffer, boxed: false)}
|
||||||
|
serializeInt32(source, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 11) != 0 {serializeString(about!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 13) != 0 {serializeInt64(raiseHandRating!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 6) != 0 {video!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 14) != 0 {presentation!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 16) != 0 {serializeInt64(paidStarsTotal!, buffer: buffer, boxed: false)}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .groupCallParticipant(let flags, let peer, let date, let activeDate, let source, let volume, let about, let raiseHandRating, let video, let presentation, let paidStarsTotal):
|
||||||
|
return ("groupCallParticipant", [("flags", flags as Any), ("peer", peer as Any), ("date", date as Any), ("activeDate", activeDate as Any), ("source", source as Any), ("volume", volume as Any), ("about", about as Any), ("raiseHandRating", raiseHandRating as Any), ("video", video as Any), ("presentation", presentation as Any), ("paidStarsTotal", paidStarsTotal as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_groupCallParticipant(_ reader: BufferReader) -> GroupCallParticipant? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Api.Peer?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
|
}
|
||||||
|
var _3: Int32?
|
||||||
|
_3 = reader.readInt32()
|
||||||
|
var _4: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
|
||||||
|
var _5: Int32?
|
||||||
|
_5 = reader.readInt32()
|
||||||
|
var _6: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 7) != 0 {_6 = reader.readInt32() }
|
||||||
|
var _7: String?
|
||||||
|
if Int(_1!) & Int(1 << 11) != 0 {_7 = parseString(reader) }
|
||||||
|
var _8: Int64?
|
||||||
|
if Int(_1!) & Int(1 << 13) != 0 {_8 = reader.readInt64() }
|
||||||
|
var _9: Api.GroupCallParticipantVideo?
|
||||||
|
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_9 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo
|
||||||
|
} }
|
||||||
|
var _10: Api.GroupCallParticipantVideo?
|
||||||
|
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_10 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo
|
||||||
|
} }
|
||||||
|
var _11: Int64?
|
||||||
|
if Int(_1!) & Int(1 << 16) != 0 {_11 = reader.readInt64() }
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
|
||||||
|
let _c5 = _5 != nil
|
||||||
|
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
|
||||||
|
let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil
|
||||||
|
let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil
|
||||||
|
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
|
||||||
|
let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil
|
||||||
|
let _c11 = (Int(_1!) & Int(1 << 16) == 0) || _11 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
||||||
|
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10, paidStarsTotal: _11)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum GroupCallParticipantVideo: TypeConstructorDescription {
|
enum GroupCallParticipantVideo: TypeConstructorDescription {
|
||||||
case groupCallParticipantVideo(flags: Int32, endpoint: String, sourceGroups: [Api.GroupCallParticipantVideoSourceGroup], audioSource: Int32?)
|
case groupCallParticipantVideo(flags: Int32, endpoint: String, sourceGroups: [Api.GroupCallParticipantVideoSourceGroup], audioSource: Int32?)
|
||||||
@ -1190,59 +1272,3 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api {
|
|
||||||
enum InputBusinessBotRecipients: TypeConstructorDescription {
|
|
||||||
case inputBusinessBotRecipients(flags: Int32, users: [Api.InputUser]?, excludeUsers: [Api.InputUser]?)
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .inputBusinessBotRecipients(let flags, let users, let excludeUsers):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(-991587810)
|
|
||||||
}
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(users!.count))
|
|
||||||
for item in users! {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}}
|
|
||||||
if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(excludeUsers!.count))
|
|
||||||
for item in excludeUsers! {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .inputBusinessBotRecipients(let flags, let users, let excludeUsers):
|
|
||||||
return ("inputBusinessBotRecipients", [("flags", flags as Any), ("users", users as Any), ("excludeUsers", excludeUsers as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_inputBusinessBotRecipients(_ reader: BufferReader) -> InputBusinessBotRecipients? {
|
|
||||||
var _1: Int32?
|
|
||||||
_1 = reader.readInt32()
|
|
||||||
var _2: [Api.InputUser]?
|
|
||||||
if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() {
|
|
||||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self)
|
|
||||||
} }
|
|
||||||
var _3: [Api.InputUser]?
|
|
||||||
if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() {
|
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self)
|
|
||||||
} }
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil
|
|
||||||
let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil
|
|
||||||
if _c1 && _c2 && _c3 {
|
|
||||||
return Api.InputBusinessBotRecipients.inputBusinessBotRecipients(flags: _1!, users: _2, excludeUsers: _3)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,59 @@
|
|||||||
|
public extension Api {
|
||||||
|
enum InputBusinessBotRecipients: TypeConstructorDescription {
|
||||||
|
case inputBusinessBotRecipients(flags: Int32, users: [Api.InputUser]?, excludeUsers: [Api.InputUser]?)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .inputBusinessBotRecipients(let flags, let users, let excludeUsers):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-991587810)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users!.count))
|
||||||
|
for item in users! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(excludeUsers!.count))
|
||||||
|
for item in excludeUsers! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .inputBusinessBotRecipients(let flags, let users, let excludeUsers):
|
||||||
|
return ("inputBusinessBotRecipients", [("flags", flags as Any), ("users", users as Any), ("excludeUsers", excludeUsers as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_inputBusinessBotRecipients(_ reader: BufferReader) -> InputBusinessBotRecipients? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: [Api.InputUser]?
|
||||||
|
if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self)
|
||||||
|
} }
|
||||||
|
var _3: [Api.InputUser]?
|
||||||
|
if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self)
|
||||||
|
} }
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil
|
||||||
|
let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.InputBusinessBotRecipients.inputBusinessBotRecipients(flags: _1!, users: _2, excludeUsers: _3)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum InputBusinessChatLink: TypeConstructorDescription {
|
enum InputBusinessChatLink: TypeConstructorDescription {
|
||||||
case inputBusinessChatLink(flags: Int32, message: String, entities: [Api.MessageEntity]?, title: String?)
|
case inputBusinessChatLink(flags: Int32, message: String, entities: [Api.MessageEntity]?, title: String?)
|
||||||
|
|||||||
@ -832,7 +832,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private let messagesStatePromise = Promise<GroupCallMessagesContext.State>(GroupCallMessagesContext.State(messages: [], pinnedMessages: []))
|
private let messagesStatePromise = Promise<GroupCallMessagesContext.State>(GroupCallMessagesContext.State(messages: [], pinnedMessages: [], topStars: [], totalStars: 0, pendingMyStars: 0))
|
||||||
public var messagesState: Signal<GroupCallMessagesContext.State, NoError> {
|
public var messagesState: Signal<GroupCallMessagesContext.State, NoError> {
|
||||||
return self.messagesStatePromise.get()
|
return self.messagesStatePromise.get()
|
||||||
}
|
}
|
||||||
@ -4061,9 +4061,27 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteMessage(id: GroupCallMessagesContext.Message.Id) {
|
public func sendStars(amount: Int64, delay: Bool) {
|
||||||
if let messagesContext = self.messagesContext {
|
if let messagesContext = self.messagesContext {
|
||||||
messagesContext.deleteMessage(id: id)
|
messagesContext.sendStars(fromId: self.joinAsPeerId, amount: amount, delay: delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func cancelSendStars() {
|
||||||
|
if let messagesContext = self.messagesContext {
|
||||||
|
messagesContext.cancelSendStars()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func commitSendStars() {
|
||||||
|
if let messagesContext = self.messagesContext {
|
||||||
|
messagesContext.commitSendStars()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func deleteMessage(id: GroupCallMessagesContext.Message.Id, reportSpam: Bool) {
|
||||||
|
if let messagesContext = self.messagesContext {
|
||||||
|
messagesContext.deleteMessage(id: id, reportSpam: reportSpam)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3761,13 +3761,54 @@ public final class GroupCallMessagesContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class TopStarsItem: Equatable {
|
||||||
|
public let peerId: EnginePeer.Id?
|
||||||
|
public let amount: Int64
|
||||||
|
public let isTop: Bool
|
||||||
|
public let isMy: Bool
|
||||||
|
public let isAnonymous: Bool
|
||||||
|
|
||||||
|
public init(peerId: EnginePeer.Id?, amount: Int64, isTop: Bool, isMy: Bool, isAnonymous: Bool) {
|
||||||
|
self.peerId = peerId
|
||||||
|
self.amount = amount
|
||||||
|
self.isTop = isTop
|
||||||
|
self.isMy = isMy
|
||||||
|
self.isAnonymous = isAnonymous
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: TopStarsItem, rhs: TopStarsItem) -> Bool {
|
||||||
|
if lhs.peerId != rhs.peerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.amount != rhs.amount {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isTop != rhs.isTop {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isMy != rhs.isMy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isAnonymous != rhs.isAnonymous {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct State: Equatable {
|
public struct State: Equatable {
|
||||||
public var messages: [Message]
|
public var messages: [Message]
|
||||||
public var pinnedMessages: [Message]
|
public var pinnedMessages: [Message]
|
||||||
|
public var topStars: [TopStarsItem]
|
||||||
|
public var totalStars: Int64
|
||||||
|
public var pendingMyStars: Int64
|
||||||
|
|
||||||
public init(messages: [Message], pinnedMessages: [Message]) {
|
public init(messages: [Message], pinnedMessages: [Message], topStars: [TopStarsItem], totalStars: Int64, pendingMyStars: Int64) {
|
||||||
self.messages = messages
|
self.messages = messages
|
||||||
self.pinnedMessages = pinnedMessages
|
self.pinnedMessages = pinnedMessages
|
||||||
|
self.topStars = topStars
|
||||||
|
self.totalStars = totalStars
|
||||||
|
self.pendingMyStars = pendingMyStars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3789,12 +3830,19 @@ public final class GroupCallMessagesContext {
|
|||||||
let stateValue = ValuePromise<State>()
|
let stateValue = ValuePromise<State>()
|
||||||
|
|
||||||
var updatesDisposable: Disposable?
|
var updatesDisposable: Disposable?
|
||||||
|
|
||||||
|
var didInitializeTopStars: Bool = false
|
||||||
|
var pollTopStarsDisposable: Disposable?
|
||||||
|
|
||||||
let sendMessageDisposables = DisposableSet()
|
let sendMessageDisposables = DisposableSet()
|
||||||
|
|
||||||
var processedIds = Set<Int64>()
|
var processedIds = Set<Int64>()
|
||||||
|
|
||||||
private var messageLifeTimer: SwiftSignalKit.Timer?
|
private var messageLifeTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
private var pendingSendStars: (fromId: PeerId, messageId: Int64, amount: Int64)?
|
||||||
|
private var pendingSendStarsTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
init(queue: Queue, account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?, messageLifetime: Int32, isLiveStream: Bool) {
|
init(queue: Queue, account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?, messageLifetime: Int32, isLiveStream: Bool) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.account = account
|
self.account = account
|
||||||
@ -3804,9 +3852,10 @@ public final class GroupCallMessagesContext {
|
|||||||
self.messageLifetime = messageLifetime
|
self.messageLifetime = messageLifetime
|
||||||
self.isLiveStream = isLiveStream
|
self.isLiveStream = isLiveStream
|
||||||
|
|
||||||
self.state = State(messages: [], pinnedMessages: [])
|
self.state = State(messages: [], pinnedMessages: [], topStars: [], totalStars: 0, pendingMyStars: 0)
|
||||||
self.stateValue.set(self.state)
|
self.stateValue.set(self.state)
|
||||||
|
|
||||||
|
let accountPeerId = account.peerId
|
||||||
self.updatesDisposable = (account.stateManager.groupCallMessageUpdates
|
self.updatesDisposable = (account.stateManager.groupCallMessageUpdates
|
||||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] updates in
|
|> deliverOn(self.queue)).startStrict(next: { [weak self] updates in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -3913,13 +3962,20 @@ public final class GroupCallMessagesContext {
|
|||||||
}
|
}
|
||||||
existingIds.insert(message.id)
|
existingIds.insert(message.id)
|
||||||
state.messages.append(message)
|
state.messages.append(message)
|
||||||
if self.isLiveStream && message.paidStars != nil {
|
if self.isLiveStream, let paidStars = message.paidStars {
|
||||||
if message.date + message.lifetime >= currentTime {
|
if message.date + message.lifetime >= currentTime {
|
||||||
state.pinnedMessages.append(message)
|
state.pinnedMessages.append(message)
|
||||||
}
|
}
|
||||||
|
if let author = message.author {
|
||||||
|
if self.didInitializeTopStars {
|
||||||
|
Impl.addStateStars(state: &state, peerId: author.id, isMy: author.id == accountPeerId, amount: paidStars)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
self.didInitializeTopStars = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -3929,12 +3985,76 @@ public final class GroupCallMessagesContext {
|
|||||||
}, queue: self.queue)
|
}, queue: self.queue)
|
||||||
self.messageLifeTimer = timer
|
self.messageLifeTimer = timer
|
||||||
timer.start()
|
timer.start()
|
||||||
|
|
||||||
|
self.pollTopStars()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.updatesDisposable?.dispose()
|
self.updatesDisposable?.dispose()
|
||||||
self.sendMessageDisposables.dispose()
|
self.sendMessageDisposables.dispose()
|
||||||
self.messageLifeTimer?.invalidate()
|
self.messageLifeTimer?.invalidate()
|
||||||
|
self.pollTopStarsDisposable?.dispose()
|
||||||
|
self.pendingSendStarsTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func pollTopStars() {
|
||||||
|
let accountPeerId = self.account.peerId
|
||||||
|
let postbox = self.account.postbox
|
||||||
|
self.pollTopStarsDisposable?.dispose()
|
||||||
|
self.pollTopStarsDisposable = ((self.account.network.request(Api.functions.phone.getGroupCallStars(call: self.reference.apiInputGroupCall))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.phone.GroupCallStars?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> then(Signal<Api.phone.GroupCallStars?, NoError>.complete() |> delay(30.0, queue: self.queue))) |> restart
|
||||||
|
|> mapToSignal { result -> Signal<(Api.phone.GroupCallStars, [PeerId: Peer])?, NoError> in
|
||||||
|
guard let result else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return postbox.transaction { transaction -> (Api.phone.GroupCallStars, [PeerId: Peer])? in
|
||||||
|
var peers: [PeerId: Peer] = [:]
|
||||||
|
switch result {
|
||||||
|
case let .groupCallStars(_, topDonors, chats, users):
|
||||||
|
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(chats: chats, users: users))
|
||||||
|
for topDonor in topDonors {
|
||||||
|
switch topDonor {
|
||||||
|
case let .groupCallDonor(_, peerId, _):
|
||||||
|
if let peerId {
|
||||||
|
if peers[peerId.peerId] == nil, let peer = transaction.getPeer(peerId.peerId) {
|
||||||
|
peers[peer.id] = peer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (result, peers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOn(self.queue)).startStrict(next: { [weak self] result in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let (result, _) = result {
|
||||||
|
switch result {
|
||||||
|
case let .groupCallStars(totalStars, topDonors, _, _):
|
||||||
|
var state = self.state
|
||||||
|
state.topStars = topDonors.map { topDonor in
|
||||||
|
switch topDonor {
|
||||||
|
case let .groupCallDonor(flags, peerId, stars):
|
||||||
|
return TopStarsItem(
|
||||||
|
peerId: peerId?.peerId,
|
||||||
|
amount: stars,
|
||||||
|
isTop: (flags & (1 << 0)) != 0,
|
||||||
|
isMy: (flags & (1 << 1)) != 0 || peerId?.peerId == accountPeerId,
|
||||||
|
isAnonymous: (flags & (1 << 2)) != 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.totalStars = totalStars
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func messageLifetimeTick() {
|
private func messageLifetimeTick() {
|
||||||
@ -3967,6 +4087,96 @@ public final class GroupCallMessagesContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func addStateStars(state: inout State, peerId: EnginePeer.Id, isMy: Bool, amount: Int64) {
|
||||||
|
state.totalStars += amount
|
||||||
|
|
||||||
|
var totalMyAmount: Int64 = amount
|
||||||
|
if isMy {
|
||||||
|
if let index = state.topStars.firstIndex(where: { $0.isMy }) {
|
||||||
|
totalMyAmount += state.topStars[index].amount
|
||||||
|
|
||||||
|
state.topStars[index] = TopStarsItem(
|
||||||
|
peerId: peerId,
|
||||||
|
amount: totalMyAmount,
|
||||||
|
isTop: false,
|
||||||
|
isMy: true,
|
||||||
|
isAnonymous: state.topStars[index].isAnonymous
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.topStars.append(TopStarsItem(
|
||||||
|
peerId: peerId,
|
||||||
|
amount: totalMyAmount,
|
||||||
|
isTop: false,
|
||||||
|
isMy: true,
|
||||||
|
isAnonymous: false
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let index = state.topStars.firstIndex(where: { $0.peerId == peerId }) {
|
||||||
|
totalMyAmount += state.topStars[index].amount
|
||||||
|
|
||||||
|
state.topStars[index] = TopStarsItem(
|
||||||
|
peerId: peerId,
|
||||||
|
amount: totalMyAmount,
|
||||||
|
isTop: false,
|
||||||
|
isMy: false,
|
||||||
|
isAnonymous: state.topStars[index].isAnonymous
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.topStars.append(TopStarsItem(
|
||||||
|
peerId: peerId,
|
||||||
|
amount: totalMyAmount,
|
||||||
|
isTop: false,
|
||||||
|
isMy: false,
|
||||||
|
isAnonymous: false
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.topStars.sort(by: { lhs, rhs in
|
||||||
|
if lhs.amount != rhs.amount {
|
||||||
|
return lhs.amount > rhs.amount
|
||||||
|
}
|
||||||
|
if let lhsPeer = lhs.peerId, let rhsPeer = rhs.peerId {
|
||||||
|
return lhsPeer < rhsPeer
|
||||||
|
}
|
||||||
|
if (lhs.peerId == nil) != (rhs.peerId == nil) {
|
||||||
|
return lhs.peerId != nil
|
||||||
|
}
|
||||||
|
return !lhs.isAnonymous
|
||||||
|
})
|
||||||
|
|
||||||
|
if let index = state.topStars.firstIndex(where: { item in
|
||||||
|
if isMy {
|
||||||
|
return item.isMy
|
||||||
|
} else {
|
||||||
|
return item.peerId == peerId
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
let item = state.topStars[index]
|
||||||
|
if index > 3 {
|
||||||
|
if isMy {
|
||||||
|
state.topStars[index] = TopStarsItem(
|
||||||
|
peerId: item.peerId,
|
||||||
|
amount: item.amount,
|
||||||
|
isTop: false,
|
||||||
|
isMy: item.isMy,
|
||||||
|
isAnonymous: item.isAnonymous
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.topStars.remove(at: index)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.topStars[index] = TopStarsItem(
|
||||||
|
peerId: item.peerId,
|
||||||
|
amount: item.amount,
|
||||||
|
isTop: true,
|
||||||
|
isMy: item.isMy,
|
||||||
|
isAnonymous: item.isAnonymous
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func send(fromId: EnginePeer.Id, randomId requestedRandomId: Int64?, text: String, entities: [MessageTextEntity], paidStars: Int64?) {
|
func send(fromId: EnginePeer.Id, randomId requestedRandomId: Int64?, text: String, entities: [MessageTextEntity], paidStars: Int64?) {
|
||||||
let _ = (self.account.postbox.transaction { transaction -> Peer? in
|
let _ = (self.account.postbox.transaction { transaction -> Peer? in
|
||||||
return transaction.getPeer(fromId)
|
return transaction.getPeer(fromId)
|
||||||
@ -4004,19 +4214,15 @@ public final class GroupCallMessagesContext {
|
|||||||
)
|
)
|
||||||
state.messages.append(message)
|
state.messages.append(message)
|
||||||
if self.isLiveStream {
|
if self.isLiveStream {
|
||||||
if paidStars != nil {
|
if let paidStars {
|
||||||
state.pinnedMessages.append(message)
|
state.pinnedMessages.append(message)
|
||||||
|
if let fromPeer {
|
||||||
|
Impl.addStateStars(state: &state, peerId: fromPeer.id, isMy: true, amount: paidStars)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
var paidStars = paidStars
|
|
||||||
if "".isEmpty {
|
|
||||||
paidStars = nil
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
self.processedIds.insert(randomId)
|
self.processedIds.insert(randomId)
|
||||||
|
|
||||||
if let e2eContext = self.e2eContext, let messageData = serializeGroupCallMessage(randomId: randomId, text: text, entities: entities) {
|
if let e2eContext = self.e2eContext, let messageData = serializeGroupCallMessage(randomId: randomId, text: text, entities: entities) {
|
||||||
@ -4044,7 +4250,7 @@ public final class GroupCallMessagesContext {
|
|||||||
flags |= 1 << 0
|
flags |= 1 << 0
|
||||||
}
|
}
|
||||||
self.sendMessageDisposables.add((self.account.network.request(Api.functions.phone.sendGroupCallMessage(
|
self.sendMessageDisposables.add((self.account.network.request(Api.functions.phone.sendGroupCallMessage(
|
||||||
flags: 0,
|
flags: flags,
|
||||||
call: self.reference.apiInputGroupCall,
|
call: self.reference.apiInputGroupCall,
|
||||||
randomId: randomId,
|
randomId: randomId,
|
||||||
message: .textWithEntities(
|
message: .textWithEntities(
|
||||||
@ -4080,7 +4286,186 @@ public final class GroupCallMessagesContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteMessage(id: Message.Id) {
|
func commitSendStars() {
|
||||||
|
guard let pendingSendStars = self.pendingSendStars else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.pendingSendStars = nil
|
||||||
|
|
||||||
|
if let _ = self.e2eContext {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let pendingSendStarsTimer = self.pendingSendStarsTimer {
|
||||||
|
self.pendingSendStarsTimer = nil
|
||||||
|
pendingSendStarsTimer.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags: Int32 = 0
|
||||||
|
flags |= 1 << 0
|
||||||
|
self.sendMessageDisposables.add((self.account.network.request(Api.functions.phone.sendGroupCallMessage(
|
||||||
|
flags: flags,
|
||||||
|
call: self.reference.apiInputGroupCall,
|
||||||
|
randomId: pendingSendStars.messageId,
|
||||||
|
message: .textWithEntities(
|
||||||
|
text: "",
|
||||||
|
entities: []
|
||||||
|
),
|
||||||
|
allowPaidStars: pendingSendStars.amount
|
||||||
|
)) |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.account.stateManager.addUpdates(updates)
|
||||||
|
for update in updates.allUpdates {
|
||||||
|
if case let .updateMessageID(id, randomIdValue) = update {
|
||||||
|
if randomIdValue == pendingSendStars.messageId {
|
||||||
|
self.processedIds.insert(Int64(id))
|
||||||
|
var state = self.state
|
||||||
|
if let index = state.messages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStars.messageId) }) {
|
||||||
|
state.messages[index] = state.messages[index].withId(Message.Id(space: .remote, id: Int64(id)))
|
||||||
|
}
|
||||||
|
if let index = state.pinnedMessages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStars.messageId) }) {
|
||||||
|
state.pinnedMessages[index] = state.pinnedMessages[index].withId(Message.Id(space: .remote, id: Int64(id)))
|
||||||
|
}
|
||||||
|
Impl.addStateStars(state: &state, peerId: pendingSendStars.fromId, isMy: true, amount: pendingSendStars.amount)
|
||||||
|
state.pendingMyStars = 0
|
||||||
|
self.state = state
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelSendStars() {
|
||||||
|
if let pendingSendStarsTimer = self.pendingSendStarsTimer {
|
||||||
|
self.pendingSendStarsTimer = nil
|
||||||
|
pendingSendStarsTimer.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let pendingSendStars = self.pendingSendStars {
|
||||||
|
self.pendingSendStars = nil
|
||||||
|
|
||||||
|
var state = self.state
|
||||||
|
state.pendingMyStars = 0
|
||||||
|
if let index = state.messages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStars.messageId) }) {
|
||||||
|
state.messages.remove(at: index)
|
||||||
|
}
|
||||||
|
if let index = state.pinnedMessages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStars.messageId) }) {
|
||||||
|
state.pinnedMessages.remove(at: index)
|
||||||
|
}
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendStars(fromId: EnginePeer.Id, amount: Int64, delay: Bool) {
|
||||||
|
let _ = (self.account.postbox.transaction { transaction -> Peer? in
|
||||||
|
return transaction.getPeer(fromId)
|
||||||
|
}
|
||||||
|
|> deliverOn(self.queue)).startStandalone(next: { [weak self] fromPeer in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
|
||||||
|
let totalAmount: Int64
|
||||||
|
if let pendingSendStarsValue = self.pendingSendStars {
|
||||||
|
totalAmount = pendingSendStarsValue.amount + amount
|
||||||
|
|
||||||
|
self.pendingSendStars = (
|
||||||
|
fromId: fromId,
|
||||||
|
messageId: pendingSendStarsValue.messageId,
|
||||||
|
amount: totalAmount
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
totalAmount = amount
|
||||||
|
|
||||||
|
var randomId: Int64 = 0
|
||||||
|
arc4random_buf(&randomId, 8)
|
||||||
|
|
||||||
|
self.pendingSendStars = (
|
||||||
|
fromId: fromId,
|
||||||
|
messageId: randomId,
|
||||||
|
amount: amount
|
||||||
|
)
|
||||||
|
|
||||||
|
self.processedIds.insert(randomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lifetime = Int32(GroupCallMessagesContext.getStarAmountParamMapping(value: totalAmount).period)
|
||||||
|
|
||||||
|
var state = self.state
|
||||||
|
if let pendingSendStarsValue = self.pendingSendStars {
|
||||||
|
if let index = state.messages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStarsValue.messageId) }) {
|
||||||
|
let message = state.messages[index]
|
||||||
|
state.messages.remove(at: index)
|
||||||
|
state.messages.append(Message(
|
||||||
|
id: message.id,
|
||||||
|
author: message.author,
|
||||||
|
text: message.text,
|
||||||
|
entities: message.entities,
|
||||||
|
date: currentTime,
|
||||||
|
lifetime: lifetime,
|
||||||
|
paidStars: totalAmount
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
state.messages.append(Message(
|
||||||
|
id: Message.Id(space: .local, id: pendingSendStarsValue.messageId),
|
||||||
|
author: fromPeer.flatMap(EnginePeer.init),
|
||||||
|
text: "",
|
||||||
|
entities: [],
|
||||||
|
date: currentTime,
|
||||||
|
lifetime: lifetime,
|
||||||
|
paidStars: totalAmount
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if let index = state.pinnedMessages.firstIndex(where: { $0.id == Message.Id(space: .local, id: pendingSendStarsValue.messageId) }) {
|
||||||
|
let message = state.pinnedMessages[index]
|
||||||
|
state.pinnedMessages.remove(at: index)
|
||||||
|
state.pinnedMessages.append(Message(
|
||||||
|
id: message.id,
|
||||||
|
author: message.author,
|
||||||
|
text: message.text,
|
||||||
|
entities: message.entities,
|
||||||
|
date: currentTime,
|
||||||
|
lifetime: lifetime,
|
||||||
|
paidStars: totalAmount
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
state.pinnedMessages.append(Message(
|
||||||
|
id: Message.Id(space: .local, id: pendingSendStarsValue.messageId),
|
||||||
|
author: fromPeer.flatMap(EnginePeer.init),
|
||||||
|
text: "",
|
||||||
|
entities: [],
|
||||||
|
date: currentTime,
|
||||||
|
lifetime: lifetime,
|
||||||
|
paidStars: totalAmount
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay {
|
||||||
|
state.pendingMyStars += amount
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
self.pendingSendStarsTimer?.invalidate()
|
||||||
|
self.pendingSendStarsTimer = SwiftSignalKit.Timer(timeout: 5.0, repeat: false, completion: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.commitSendStars()
|
||||||
|
}, queue: self.queue)
|
||||||
|
self.pendingSendStarsTimer?.start()
|
||||||
|
} else {
|
||||||
|
self.state = state
|
||||||
|
self.commitSendStars()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteMessage(id: Message.Id, reportSpam: Bool) {
|
||||||
var updatedState: State?
|
var updatedState: State?
|
||||||
if let index = self.state.messages.firstIndex(where: { $0.id == id }) {
|
if let index = self.state.messages.firstIndex(where: { $0.id == id }) {
|
||||||
if updatedState == nil {
|
if updatedState == nil {
|
||||||
@ -4097,6 +4482,12 @@ public final class GroupCallMessagesContext {
|
|||||||
if let updatedState {
|
if let updatedState {
|
||||||
self.state = updatedState
|
self.state = updatedState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var flags: Int32 = 0
|
||||||
|
if reportSpam {
|
||||||
|
flags |= 1 << 0
|
||||||
|
}
|
||||||
|
let _ = self.account.network.request(Api.functions.phone.deleteGroupCallMessages(flags: flags, call: self.reference.apiInputGroupCall, messages: [Int32(clamping: id.id)])).startStandalone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4123,9 +4514,27 @@ public final class GroupCallMessagesContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteMessage(id: Message.Id) {
|
public func sendStars(fromId: EnginePeer.Id, amount: Int64, delay: Bool) {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
impl.deleteMessage(id: id)
|
impl.sendStars(fromId: fromId, amount: amount, delay: delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func cancelSendStars() {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.cancelSendStars()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func commitSendStars() {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.commitSendStars()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func deleteMessage(id: Message.Id, reportSpam: Bool) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.deleteMessage(id: id, reportSpam: reportSpam)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -427,6 +427,14 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func calculateLiveStreamResult() -> AdminUserActionsSheet.LiveStreamResult {
|
||||||
|
return AdminUserActionsSheet.LiveStreamResult(
|
||||||
|
reportSpam: !self.optionReportSelectedPeers.isEmpty,
|
||||||
|
deleteAll: !self.optionDeleteAllSelectedPeers.isEmpty,
|
||||||
|
ban: !self.optionBanSelectedPeers.isEmpty
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: ComponentTransition) {
|
private func updateScrolling(transition: ComponentTransition) {
|
||||||
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
|
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
@ -578,7 +586,7 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
|
self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor
|
||||||
|
|
||||||
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||||
self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
|
self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
|
||||||
@ -663,6 +671,9 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case .liveStream:
|
||||||
|
availableOptions.append(.deleteAll)
|
||||||
|
availableOptions.append(.ban)
|
||||||
}
|
}
|
||||||
|
|
||||||
let optionsItem: (OptionsSection) -> AnyComponentWithIdentity<Empty> = { section in
|
let optionsItem: (OptionsSection) -> AnyComponentWithIdentity<Empty> = { section in
|
||||||
@ -912,6 +923,13 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount))
|
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .liveStream(messageCount, deleteAllMessageCount, _):
|
||||||
|
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(messageCount))
|
||||||
|
if let deleteAllMessageCount {
|
||||||
|
if self.optionDeleteAllSelectedPeers == Set(component.peers.map(\.peer.id)) {
|
||||||
|
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
@ -965,6 +983,7 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
transition: optionsSectionTransition,
|
transition: optionsSectionTransition,
|
||||||
component: AnyComponent(ListSectionComponent(
|
component: AnyComponent(ListSectionComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
|
style: .glass,
|
||||||
header: AnyComponent(MultilineTextComponent(
|
header: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: environment.strings.Chat_AdminActionSheet_RestrictSectionHeader,
|
string: environment.strings.Chat_AdminActionSheet_RestrictSectionHeader,
|
||||||
@ -1042,6 +1061,7 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case let .channel(channel) = component.chatPeer, channel.isMonoForum {
|
if case let .channel(channel) = component.chatPeer, channel.isMonoForum {
|
||||||
|
} else if case .liveStream = component.mode {
|
||||||
} else {
|
} else {
|
||||||
var allConfigItems: [(ConfigItem, Bool)] = []
|
var allConfigItems: [(ConfigItem, Bool)] = []
|
||||||
if !self.allowedMediaRights.isEmpty || !self.allowedParticipantRights.isEmpty {
|
if !self.allowedMediaRights.isEmpty || !self.allowedParticipantRights.isEmpty {
|
||||||
@ -1362,9 +1382,11 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ButtonComponent(
|
component: AnyComponent(ButtonComponent(
|
||||||
background: ButtonComponent.Background(
|
background: ButtonComponent.Background(
|
||||||
|
style: .glass,
|
||||||
color: environment.theme.list.itemCheckColors.fillColor,
|
color: environment.theme.list.itemCheckColors.fillColor,
|
||||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||||
|
cornerRadius: 54.0 * 0.5
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(
|
content: AnyComponentWithIdentity(
|
||||||
id: AnyHashable(0),
|
id: AnyHashable(0),
|
||||||
@ -1389,11 +1411,13 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
completion(self.calculateMonoforumResult())
|
completion(self.calculateMonoforumResult())
|
||||||
case let .chat(_, _, completion):
|
case let .chat(_, _, completion):
|
||||||
completion(self.calculateChatResult())
|
completion(self.calculateChatResult())
|
||||||
|
case let .liveStream(_, _, completion):
|
||||||
|
completion(self.calculateLiveStreamResult())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 54.0)
|
||||||
)
|
)
|
||||||
let bottomPanelHeight = 8.0 + environment.safeInsets.bottom + actionButtonSize.height
|
let bottomPanelHeight = 8.0 + environment.safeInsets.bottom + actionButtonSize.height
|
||||||
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
|
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
|
||||||
@ -1462,6 +1486,7 @@ private final class AdminUserActionsSheetComponent: Component {
|
|||||||
public class AdminUserActionsSheet: ViewControllerComponentContainer {
|
public class AdminUserActionsSheet: ViewControllerComponentContainer {
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
case chat(messageCount: Int, deleteAllMessageCount: Int?, completion: (ChatResult) -> Void)
|
case chat(messageCount: Int, deleteAllMessageCount: Int?, completion: (ChatResult) -> Void)
|
||||||
|
case liveStream(messageCount: Int, deleteAllMessageCount: Int?, completion: (LiveStreamResult) -> Void)
|
||||||
case monoforum(completion: (MonoforumResult) -> Void)
|
case monoforum(completion: (MonoforumResult) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1479,6 +1504,18 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class LiveStreamResult {
|
||||||
|
public let reportSpam: Bool
|
||||||
|
public let deleteAll: Bool
|
||||||
|
public let ban: Bool
|
||||||
|
|
||||||
|
init(reportSpam: Bool, deleteAll: Bool, ban: Bool) {
|
||||||
|
self.reportSpam = reportSpam
|
||||||
|
self.deleteAll = deleteAll
|
||||||
|
self.ban = ban
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class MonoforumResult {
|
public final class MonoforumResult {
|
||||||
public let ban: Bool
|
public let ban: Bool
|
||||||
public let reportSpam: Bool
|
public let reportSpam: Bool
|
||||||
@ -1493,9 +1530,9 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], mode: Mode) {
|
public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], mode: Mode, customTheme: PresentationTheme? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, mode: mode), navigationBarAppearance: .none)
|
super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, mode: mode), navigationBarAppearance: .none, theme: customTheme.flatMap({ .custom($0) }) ?? .default)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
|
|||||||
@ -27,6 +27,9 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/TelegramUI/Components/GlassControls",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -18,8 +18,11 @@ import TelegramNotices
|
|||||||
import GlassBackgroundComponent
|
import GlassBackgroundComponent
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import ComponentDisplayAdapters
|
import ComponentDisplayAdapters
|
||||||
|
import GlassControls
|
||||||
|
import BundleIconComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
|
||||||
private enum SubscriberAction: Equatable {
|
private enum SubscriberAction: Equatable, Hashable {
|
||||||
case join
|
case join
|
||||||
case joinGroup
|
case joinGroup
|
||||||
case applyToJoin
|
case applyToJoin
|
||||||
@ -143,7 +146,10 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState:
|
|||||||
private let badgeFont = Font.regular(14.0)
|
private let badgeFont = Font.regular(14.0)
|
||||||
|
|
||||||
public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||||
private let buttonBackgroundView: GlassBackgroundView
|
private let panelContainer = UIView()
|
||||||
|
private let panel = ComponentView<Empty>()
|
||||||
|
|
||||||
|
/*private let buttonBackgroundView: GlassBackgroundView
|
||||||
private let button: HighlightableButton
|
private let button: HighlightableButton
|
||||||
private let buttonTitle: ImmediateTextNode
|
private let buttonTitle: ImmediateTextNode
|
||||||
private let buttonTintTitle: ImmediateTextNode
|
private let buttonTintTitle: ImmediateTextNode
|
||||||
@ -158,7 +164,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
|
|
||||||
private let suggestedPostButtonBackgroundView: GlassBackgroundView
|
private let suggestedPostButtonBackgroundView: GlassBackgroundView
|
||||||
private let suggestedPostButton: HighlightableButton
|
private let suggestedPostButton: HighlightableButton
|
||||||
private let suggestedPostButtonIconView: UIImageView
|
private let suggestedPostButtonIconView: UIImageView*/
|
||||||
|
|
||||||
private var action: SubscriberAction?
|
private var action: SubscriberAction?
|
||||||
|
|
||||||
@ -171,7 +177,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)?
|
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)?
|
||||||
|
|
||||||
public override init() {
|
public override init() {
|
||||||
self.button = HighlightableButton()
|
/*self.button = HighlightableButton()
|
||||||
self.buttonBackgroundView = GlassBackgroundView()
|
self.buttonBackgroundView = GlassBackgroundView()
|
||||||
self.buttonBackgroundView.isUserInteractionEnabled = false
|
self.buttonBackgroundView.isUserInteractionEnabled = false
|
||||||
self.buttonTitle = ImmediateTextNode()
|
self.buttonTitle = ImmediateTextNode()
|
||||||
@ -203,18 +209,20 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView()
|
self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView()
|
||||||
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView)
|
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView)
|
||||||
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButton)
|
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButton)
|
||||||
self.suggestedPostButtonBackgroundView.isHidden = true
|
self.suggestedPostButtonBackgroundView.isHidden = true*/
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.view.addSubview(self.buttonBackgroundView)
|
/*self.view.addSubview(self.buttonBackgroundView)
|
||||||
self.view.addSubview(self.helpButtonBackgroundView)
|
self.view.addSubview(self.helpButtonBackgroundView)
|
||||||
self.view.addSubview(self.giftButtonBackgroundView)
|
self.view.addSubview(self.giftButtonBackgroundView)
|
||||||
self.view.addSubview(self.suggestedPostButtonBackgroundView)
|
self.view.addSubview(self.suggestedPostButtonBackgroundView)
|
||||||
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||||
self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside)
|
self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside)
|
||||||
self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside)
|
self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside)
|
||||||
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)
|
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)*/
|
||||||
|
|
||||||
|
self.view.addSubview(self.panelContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -330,11 +338,17 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
}
|
}
|
||||||
#endif*/
|
#endif*/
|
||||||
|
|
||||||
if giftCount < 2 && !self.giftButton.isHidden {
|
let giftItemView = (self.panel.view as? GlassControlPanelComponent.View)?.leftItemView?.itemView(id: AnyHashable("gift"))
|
||||||
|
let suggestPostItemView = (self.panel.view as? GlassControlPanelComponent.View)?.leftItemView?.itemView(id: AnyHashable("suggestPost"))
|
||||||
|
|
||||||
|
if giftCount < 2, let giftItemView {
|
||||||
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
|
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
|
||||||
|
|
||||||
Queue.mainQueue().after(0.4, {
|
Queue.mainQueue().after(0.4, { [weak giftItemView] in
|
||||||
let absoluteFrame = self.giftButton.convert(self.giftButton.bounds, to: parentController.view)
|
guard let giftItemView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let absoluteFrame = giftItemView.convert(giftItemView.bounds, to: parentController.view)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY), size: CGSize())
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -357,11 +371,14 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
)
|
)
|
||||||
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil)
|
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil)
|
||||||
})
|
})
|
||||||
} else if suggestCount < 2 && !self.suggestedPostButton.isHidden {
|
} else if suggestCount < 2, let suggestPostItemView {
|
||||||
let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start()
|
let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start()
|
||||||
|
|
||||||
Queue.mainQueue().after(0.4, {
|
Queue.mainQueue().after(0.4, { [weak suggestPostItemView] in
|
||||||
let absoluteFrame = self.suggestedPostButton.convert(self.suggestedPostButton.bounds, to: parentController.view)
|
guard let suggestPostItemView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let absoluteFrame = suggestPostItemView.convert(suggestPostItemView.bounds, to: parentController.view)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY), size: CGSize())
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -394,7 +411,133 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
let isFirstTime = self.layoutData == nil
|
let isFirstTime = self.layoutData == nil
|
||||||
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics)
|
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics)
|
||||||
|
|
||||||
if self.presentationInterfaceState != interfaceState || force {
|
var transition = transition
|
||||||
|
if !isFirstTime && !transition.isAnimated {
|
||||||
|
transition = .animated(duration: 0.4, curve: .spring)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.presentationInterfaceState = interfaceState
|
||||||
|
|
||||||
|
var centerAction: (title: String, isAccent: Bool)?
|
||||||
|
if let context = self.context, let peer = interfaceState.renderedPeer?.peer, let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) {
|
||||||
|
self.action = action
|
||||||
|
let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
|
||||||
|
|
||||||
|
var isAccent = false
|
||||||
|
if case .join = self.action {
|
||||||
|
isAccent = true
|
||||||
|
}
|
||||||
|
centerAction = (title, isAccent)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayGift = false
|
||||||
|
var displaySuggestPost = false
|
||||||
|
var displayHelp = false
|
||||||
|
|
||||||
|
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||||
|
if case .broadcast = peer.info, interfaceState.starGiftsAvailable {
|
||||||
|
displayGift = true
|
||||||
|
}
|
||||||
|
if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
|
||||||
|
displaySuggestPost = true
|
||||||
|
}
|
||||||
|
if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
|
||||||
|
displayHelp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftInset = leftInset + 8.0
|
||||||
|
var rightInset = rightInset + 8.0
|
||||||
|
if bottomInset <= 32.0 {
|
||||||
|
leftInset += 18.0
|
||||||
|
rightInset += 18.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftPanelItems: [GlassControlGroupComponent.Item] = []
|
||||||
|
if displaySuggestPost {
|
||||||
|
leftPanelItems.append(GlassControlGroupComponent.Item(
|
||||||
|
id: "suggestPost",
|
||||||
|
content: .icon("Chat/Input/Accessory Panels/SuggestPost"),
|
||||||
|
action: { [weak self] in
|
||||||
|
self?.suggestedPostPressed()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if displayGift {
|
||||||
|
leftPanelItems.append(GlassControlGroupComponent.Item(
|
||||||
|
id: "gift",
|
||||||
|
content: .icon("Chat/Input/Accessory Panels/Gift"),
|
||||||
|
action: { [weak self] in
|
||||||
|
self?.giftPressed()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if displayHelp {
|
||||||
|
leftPanelItems.append(GlassControlGroupComponent.Item(
|
||||||
|
id: "help",
|
||||||
|
content: .icon("Chat/Input/Accessory Panels/Help"),
|
||||||
|
action: { [weak self] in
|
||||||
|
self?.helpPressed()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
var centerPanelItem: GlassControlPanelComponent.Item?
|
||||||
|
if let centerAction {
|
||||||
|
centerPanelItem = GlassControlPanelComponent.Item(
|
||||||
|
items: [GlassControlGroupComponent.Item(
|
||||||
|
id: 0,
|
||||||
|
content: .text(centerAction.title),
|
||||||
|
action: { [weak self] in
|
||||||
|
self?.buttonPressed()
|
||||||
|
}
|
||||||
|
)],
|
||||||
|
background: centerAction.isAccent ? .activeTint : .panel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rightPanelItems: [GlassControlGroupComponent.Item] = []
|
||||||
|
rightPanelItems.append(GlassControlGroupComponent.Item(
|
||||||
|
id: "search",
|
||||||
|
content: .icon("Chat List/SearchIcon"),
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
let panelHeight = defaultHeight(metrics: metrics)
|
||||||
|
let _ = isFirstTime
|
||||||
|
let panelFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight))
|
||||||
|
|
||||||
|
let _ = self.panel.update(
|
||||||
|
transition: ComponentTransition(transition),
|
||||||
|
component: AnyComponent(GlassControlPanelComponent(
|
||||||
|
theme: interfaceState.theme,
|
||||||
|
leftItem: leftPanelItems.isEmpty ? nil : GlassControlPanelComponent.Item(
|
||||||
|
items: leftPanelItems,
|
||||||
|
background: .panel
|
||||||
|
),
|
||||||
|
centralItem: centerPanelItem,
|
||||||
|
rightItem: rightPanelItems.isEmpty ? nil : GlassControlPanelComponent.Item(
|
||||||
|
items: rightPanelItems,
|
||||||
|
background: .panel
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: panelFrame.size
|
||||||
|
)
|
||||||
|
if let panelView = self.panel.view {
|
||||||
|
if panelView.superview == nil {
|
||||||
|
self.panelContainer.addSubview(panelView)
|
||||||
|
}
|
||||||
|
transition.updateFrame(view: self.panelContainer, frame: panelFrame)
|
||||||
|
transition.updateFrame(view: panelView, frame: CGRect(origin: CGPoint(), size: panelFrame.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if self.presentationInterfaceState != interfaceState || force {
|
||||||
let previousState = self.presentationInterfaceState
|
let previousState = self.presentationInterfaceState
|
||||||
self.presentationInterfaceState = interfaceState
|
self.presentationInterfaceState = interfaceState
|
||||||
|
|
||||||
@ -504,7 +647,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||||||
transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)))
|
transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)))
|
||||||
}
|
}
|
||||||
transition.updateFrame(view: self.suggestedPostButton, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))
|
transition.updateFrame(view: self.suggestedPostButton, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))
|
||||||
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))
|
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))*/
|
||||||
|
|
||||||
return panelHeight
|
return panelHeight
|
||||||
}
|
}
|
||||||
|
|||||||
@ -257,13 +257,16 @@ private final class BadgeComponent: Component {
|
|||||||
private final class PeerBadgeComponent: Component {
|
private final class PeerBadgeComponent: Component {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let title: String
|
let title: String
|
||||||
|
let color: UIColor
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
title: String
|
title: String,
|
||||||
|
color: UIColor
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.color = color
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeerBadgeComponent, rhs: PeerBadgeComponent) -> Bool {
|
static func ==(lhs: PeerBadgeComponent, rhs: PeerBadgeComponent) -> Bool {
|
||||||
@ -273,6 +276,9 @@ private final class PeerBadgeComponent: Component {
|
|||||||
if lhs.title != rhs.title {
|
if lhs.title != rhs.title {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.color != rhs.color {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +330,7 @@ private final class PeerBadgeComponent: Component {
|
|||||||
let size = CGSize(width: contentSize.width + sideInset * 2.0, height: contentSize.height + 3.0 * 2.0)
|
let size = CGSize(width: contentSize.width + sideInset * 2.0, height: contentSize.height + 3.0 * 2.0)
|
||||||
|
|
||||||
self.backgroundMaskLayer.backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.blocksBackgroundColor.cgColor : component.theme.list.plainBackgroundColor.cgColor
|
self.backgroundMaskLayer.backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.blocksBackgroundColor.cgColor : component.theme.list.plainBackgroundColor.cgColor
|
||||||
self.backgroundLayer.backgroundColor = UIColor(rgb: 0xFFB10D).cgColor
|
self.backgroundLayer.backgroundColor = component.color.cgColor
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(), size: size)
|
let backgroundFrame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.backgroundLayer.frame = backgroundFrame
|
self.backgroundLayer.frame = backgroundFrame
|
||||||
@ -370,19 +376,22 @@ private final class PeerComponent: Component {
|
|||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
let count: String
|
let count: String
|
||||||
|
let color: UIColor
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
peer: EnginePeer?,
|
peer: EnginePeer?,
|
||||||
count: String
|
count: String,
|
||||||
|
color: UIColor
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.color = color
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeerComponent, rhs: PeerComponent) -> Bool {
|
static func ==(lhs: PeerComponent, rhs: PeerComponent) -> Bool {
|
||||||
@ -401,6 +410,9 @@ private final class PeerComponent: Component {
|
|||||||
if lhs.count != rhs.count {
|
if lhs.count != rhs.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.color != rhs.color {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,7 +457,8 @@ private final class PeerComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(PeerBadgeComponent(
|
component: AnyComponent(PeerBadgeComponent(
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
title: component.count
|
title: component.count,
|
||||||
|
color: component.color
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||||
@ -2015,71 +2028,75 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
if !reactData.topPeers.isEmpty {
|
if !reactData.topPeers.isEmpty {
|
||||||
contentHeight += 3.0
|
contentHeight += 3.0
|
||||||
|
|
||||||
let topPeersLeftSeparator: SimpleLayer
|
if case .message = reactData.reactSubject {
|
||||||
if let current = self.topPeersLeftSeparator {
|
let topPeersLeftSeparator: SimpleLayer
|
||||||
topPeersLeftSeparator = current
|
if let current = self.topPeersLeftSeparator {
|
||||||
} else {
|
topPeersLeftSeparator = current
|
||||||
topPeersLeftSeparator = SimpleLayer()
|
} else {
|
||||||
self.topPeersLeftSeparator = topPeersLeftSeparator
|
topPeersLeftSeparator = SimpleLayer()
|
||||||
self.scrollContentView.layer.addSublayer(topPeersLeftSeparator)
|
self.topPeersLeftSeparator = topPeersLeftSeparator
|
||||||
}
|
self.scrollContentView.layer.addSublayer(topPeersLeftSeparator)
|
||||||
|
|
||||||
let topPeersRightSeparator: SimpleLayer
|
|
||||||
if let current = self.topPeersRightSeparator {
|
|
||||||
topPeersRightSeparator = current
|
|
||||||
} else {
|
|
||||||
topPeersRightSeparator = SimpleLayer()
|
|
||||||
self.topPeersRightSeparator = topPeersRightSeparator
|
|
||||||
self.scrollContentView.layer.addSublayer(topPeersRightSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
let topPeersTitleBackground: SimpleLayer
|
|
||||||
if let current = self.topPeersTitleBackground {
|
|
||||||
topPeersTitleBackground = current
|
|
||||||
} else {
|
|
||||||
topPeersTitleBackground = SimpleLayer()
|
|
||||||
self.topPeersTitleBackground = topPeersTitleBackground
|
|
||||||
self.scrollContentView.layer.addSublayer(topPeersTitleBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
let topPeersTitle: ComponentView<Empty>
|
|
||||||
if let current = self.topPeersTitle {
|
|
||||||
topPeersTitle = current
|
|
||||||
} else {
|
|
||||||
topPeersTitle = ComponentView()
|
|
||||||
self.topPeersTitle = topPeersTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
topPeersLeftSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
|
||||||
topPeersRightSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
|
||||||
|
|
||||||
let topPeersTitleSize = topPeersTitle.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SectionTop, font: Font.semibold(15.0), textColor: .white))
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 300.0, height: 100.0)
|
|
||||||
)
|
|
||||||
let topPeersBackgroundSize = CGSize(width: topPeersTitleSize.width + 16.0 * 2.0, height: topPeersTitleSize.height + 9.0 * 2.0)
|
|
||||||
let topPeersBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - topPeersBackgroundSize.width) * 0.5), y: contentHeight), size: topPeersBackgroundSize)
|
|
||||||
|
|
||||||
topPeersTitleBackground.backgroundColor = UIColor(rgb: 0xFFB10D).cgColor
|
|
||||||
topPeersTitleBackground.cornerRadius = topPeersBackgroundFrame.height * 0.5
|
|
||||||
transition.setFrame(layer: topPeersTitleBackground, frame: topPeersBackgroundFrame)
|
|
||||||
|
|
||||||
let topPeersTitleFrame = CGRect(origin: CGPoint(x: topPeersBackgroundFrame.minX + floor((topPeersBackgroundFrame.width - topPeersTitleSize.width) * 0.5), y: topPeersBackgroundFrame.minY + floor((topPeersBackgroundFrame.height - topPeersTitleSize.height) * 0.5)), size: topPeersTitleSize)
|
|
||||||
if let topPeersTitleView = topPeersTitle.view {
|
|
||||||
if topPeersTitleView.superview == nil {
|
|
||||||
self.scrollContentView.addSubview(topPeersTitleView)
|
|
||||||
}
|
}
|
||||||
transition.setFrame(view: topPeersTitleView, frame: topPeersTitleFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
let separatorY = topPeersBackgroundFrame.midY
|
let topPeersRightSeparator: SimpleLayer
|
||||||
let separatorSpacing: CGFloat = 10.0
|
if let current = self.topPeersRightSeparator {
|
||||||
transition.setFrame(layer: topPeersLeftSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: separatorY), size: CGSize(width: max(0.0, topPeersBackgroundFrame.minX - separatorSpacing - sideInset), height: UIScreenPixel)))
|
topPeersRightSeparator = current
|
||||||
transition.setFrame(layer: topPeersRightSeparator, frame: CGRect(origin: CGPoint(x: topPeersBackgroundFrame.maxX + separatorSpacing, y: separatorY), size: CGSize(width: max(0.0, availableSize.width - sideInset - (topPeersBackgroundFrame.maxX + separatorSpacing)), height: UIScreenPixel)))
|
} else {
|
||||||
|
topPeersRightSeparator = SimpleLayer()
|
||||||
|
self.topPeersRightSeparator = topPeersRightSeparator
|
||||||
|
self.scrollContentView.layer.addSublayer(topPeersRightSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
let topPeersTitleBackground: SimpleLayer
|
||||||
|
if let current = self.topPeersTitleBackground {
|
||||||
|
topPeersTitleBackground = current
|
||||||
|
} else {
|
||||||
|
topPeersTitleBackground = SimpleLayer()
|
||||||
|
self.topPeersTitleBackground = topPeersTitleBackground
|
||||||
|
self.scrollContentView.layer.addSublayer(topPeersTitleBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
let topPeersTitle: ComponentView<Empty>
|
||||||
|
if let current = self.topPeersTitle {
|
||||||
|
topPeersTitle = current
|
||||||
|
} else {
|
||||||
|
topPeersTitle = ComponentView()
|
||||||
|
self.topPeersTitle = topPeersTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
topPeersLeftSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||||
|
topPeersRightSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||||
|
|
||||||
|
let topPeersTitleSize = topPeersTitle.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SectionTop, font: Font.semibold(15.0), textColor: .white))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 300.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let topPeersBackgroundSize = CGSize(width: topPeersTitleSize.width + 16.0 * 2.0, height: topPeersTitleSize.height + 9.0 * 2.0)
|
||||||
|
let topPeersBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - topPeersBackgroundSize.width) * 0.5), y: contentHeight), size: topPeersBackgroundSize)
|
||||||
|
|
||||||
|
topPeersTitleBackground.backgroundColor = UIColor(rgb: 0xFFB10D).cgColor
|
||||||
|
topPeersTitleBackground.cornerRadius = topPeersBackgroundFrame.height * 0.5
|
||||||
|
transition.setFrame(layer: topPeersTitleBackground, frame: topPeersBackgroundFrame)
|
||||||
|
|
||||||
|
let topPeersTitleFrame = CGRect(origin: CGPoint(x: topPeersBackgroundFrame.minX + floor((topPeersBackgroundFrame.width - topPeersTitleSize.width) * 0.5), y: topPeersBackgroundFrame.minY + floor((topPeersBackgroundFrame.height - topPeersTitleSize.height) * 0.5)), size: topPeersTitleSize)
|
||||||
|
if let topPeersTitleView = topPeersTitle.view {
|
||||||
|
if topPeersTitleView.superview == nil {
|
||||||
|
self.scrollContentView.addSubview(topPeersTitleView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: topPeersTitleView, frame: topPeersTitleFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
let separatorY = topPeersBackgroundFrame.midY
|
||||||
|
let separatorSpacing: CGFloat = 10.0
|
||||||
|
transition.setFrame(layer: topPeersLeftSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: separatorY), size: CGSize(width: max(0.0, topPeersBackgroundFrame.minX - separatorSpacing - sideInset), height: UIScreenPixel)))
|
||||||
|
transition.setFrame(layer: topPeersRightSeparator, frame: CGRect(origin: CGPoint(x: topPeersBackgroundFrame.maxX + separatorSpacing, y: separatorY), size: CGSize(width: max(0.0, availableSize.width - sideInset - (topPeersBackgroundFrame.maxX + separatorSpacing)), height: UIScreenPixel)))
|
||||||
|
|
||||||
|
contentHeight += 60.0
|
||||||
|
}
|
||||||
|
|
||||||
var mappedTopPeers = reactData.topPeers
|
var mappedTopPeers = reactData.topPeers
|
||||||
if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) {
|
if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) {
|
||||||
@ -2142,6 +2159,12 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
|
|
||||||
let itemCountString = presentationStringsFormattedNumber(Int32(topPeer.count), environment.dateTimeFormat.groupingSeparator)
|
let itemCountString = presentationStringsFormattedNumber(Int32(topPeer.count), environment.dateTimeFormat.groupingSeparator)
|
||||||
|
|
||||||
|
var peerColor: UIColor = UIColor(rgb: 0xFFB10D)
|
||||||
|
if case .liveStream = reactData.reactSubject {
|
||||||
|
let color = GroupCallMessagesContext.getStarAmountParamMapping(value: Int64(topPeer.count)).color ?? .purple
|
||||||
|
peerColor = StoryLiveChatMessageComponent.getMessageColor(color: color)
|
||||||
|
}
|
||||||
|
|
||||||
let itemSize = itemView.update(
|
let itemSize = itemView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(PlainButtonComponent(
|
component: AnyComponent(PlainButtonComponent(
|
||||||
@ -2150,7 +2173,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
peer: topPeer.peer,
|
peer: topPeer.peer,
|
||||||
count: itemCountString
|
count: itemCountString,
|
||||||
|
color: peerColor
|
||||||
)),
|
)),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
@ -2239,7 +2263,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
itemComponentView.alpha = 0.0
|
itemComponentView.alpha = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: contentHeight + 60.0), size: itemSize)
|
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: contentHeight), size: itemSize)
|
||||||
|
|
||||||
if animateItem {
|
if animateItem {
|
||||||
itemPositionTransition.setPosition(view: itemComponentView, position: itemFrame.center)
|
itemPositionTransition.setPosition(view: itemComponentView, position: itemFrame.center)
|
||||||
@ -2255,7 +2279,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
itemX += itemSize.width + itemSpacing
|
itemX += itemSize.width + itemSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHeight += 164.0
|
contentHeight += 104.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reactData.topPeers.isEmpty {
|
if !reactData.topPeers.isEmpty {
|
||||||
@ -3197,8 +3221,8 @@ private final class SliderStarsView: UIView {
|
|||||||
self.setupEmitter()
|
self.setupEmitter()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emitterLayer.setValue(20.0 + Float(value * 40.0), forKeyPath: "emitterCells.emitter.birthRate")
|
self.emitterLayer.setValue(20.0 + Float(value * 200.0), forKeyPath: "emitterCells.emitter.birthRate")
|
||||||
self.emitterLayer.setValue(15.0 + value * 75.0, forKeyPath: "emitterCells.emitter.velocity")
|
self.emitterLayer.setValue(15.0 + value * 250.0, forKeyPath: "emitterCells.emitter.velocity")
|
||||||
|
|
||||||
self.emitterLayer.frame = CGRect(origin: .zero, size: size)
|
self.emitterLayer.frame = CGRect(origin: .zero, size: size)
|
||||||
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
|||||||
@ -217,6 +217,11 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate {
|
public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate {
|
||||||
|
private enum AudioRecordingRemoveAnimationState {
|
||||||
|
case recordingToAttachButton
|
||||||
|
case previewToAttachButton
|
||||||
|
}
|
||||||
|
|
||||||
public let textPlaceholderNode: ImmediateTextNodeWithEntities
|
public let textPlaceholderNode: ImmediateTextNodeWithEntities
|
||||||
|
|
||||||
private let glassBackgroundContainer: GlassBackgroundContainerView
|
private let glassBackgroundContainer: GlassBackgroundContainerView
|
||||||
@ -271,7 +276,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
private var searchActivityIndicator: ActivityIndicator?
|
private var searchActivityIndicator: ActivityIndicator?
|
||||||
public var audioRecordingInfoContainerNode: ASDisplayNode?
|
public var audioRecordingInfoContainerNode: ASDisplayNode?
|
||||||
public var audioRecordingDotView: UIImageView?
|
public var audioRecordingDotView: UIImageView?
|
||||||
public var audioRecordingDotNodeDismissed = false
|
private var audioRecordingRemoveAnimationState: AudioRecordingRemoveAnimationState?
|
||||||
public var audioRecordingTimeNode: ChatTextInputAudioRecordingTimeNode?
|
public var audioRecordingTimeNode: ChatTextInputAudioRecordingTimeNode?
|
||||||
public var audioRecordingCancelIndicator: ChatTextInputAudioRecordingCancelIndicator?
|
public var audioRecordingCancelIndicator: ChatTextInputAudioRecordingCancelIndicator?
|
||||||
|
|
||||||
@ -803,6 +808,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
if sendMedia {
|
if sendMedia {
|
||||||
interfaceInteraction.finishMediaRecording(.send(viewOnce: strongSelf.viewOnce))
|
interfaceInteraction.finishMediaRecording(.send(viewOnce: strongSelf.viewOnce))
|
||||||
} else {
|
} else {
|
||||||
|
strongSelf.audioRecordingRemoveAnimationState = .recordingToAttachButton
|
||||||
interfaceInteraction.finishMediaRecording(.dismiss)
|
interfaceInteraction.finishMediaRecording(.dismiss)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2256,6 +2262,46 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
audioRecordingItemsAlpha = 0.0
|
audioRecordingItemsAlpha = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let audioRecordingRemoveAnimationState = self.audioRecordingRemoveAnimationState, case .previewToAttachButton = audioRecordingRemoveAnimationState {
|
||||||
|
self.audioRecordingRemoveAnimationState = nil
|
||||||
|
|
||||||
|
let dotAnimation = ComponentView<Empty>()
|
||||||
|
let dotAnimationSize = dotAnimation.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(name: "BinBlue"),
|
||||||
|
color: interfaceState.theme.chat.inputPanel.panelControlColor,
|
||||||
|
startingPosition: .begin
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||||
|
)
|
||||||
|
if let dotAnimationView = dotAnimation.view as? LottieComponent.View {
|
||||||
|
self.attachmentButtonBackground.contentView.addSubview(dotAnimationView)
|
||||||
|
dotAnimationView.frame = dotAnimationSize.centered(in: self.attachmentButtonBackground.contentView.bounds)
|
||||||
|
|
||||||
|
self.attachmentButtonIcon.layer.opacity = 0.0
|
||||||
|
self.attachmentButtonIcon.layer.transform = CATransform3DMakeScale(0.001, 0.001, 1.0)
|
||||||
|
dotAnimationView.playOnce(completion: { [weak self, weak dotAnimationView] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
|
if let dotAnimationView {
|
||||||
|
transition.setAlpha(view: dotAnimationView, alpha: 0.0, completion: { [weak dotAnimationView] _ in
|
||||||
|
dotAnimationView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
transition.setScale(view: dotAnimationView, scale: 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setAlpha(view: self.attachmentButtonIcon, alpha: 1.0)
|
||||||
|
transition.setScale(view: self.attachmentButtonIcon, scale: 1.0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let mediaRecordingState {
|
if let mediaRecordingState {
|
||||||
audioRecordingItemsAlpha = 0.0
|
audioRecordingItemsAlpha = 0.0
|
||||||
|
|
||||||
@ -2290,9 +2336,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
animateCancelSlideIn = transition.isAnimated
|
animateCancelSlideIn = transition.isAnimated
|
||||||
|
|
||||||
audioRecordingCancelIndicator = ChatTextInputAudioRecordingCancelIndicator(theme: interfaceState.theme, strings: interfaceState.strings, cancel: { [weak self] in
|
audioRecordingCancelIndicator = ChatTextInputAudioRecordingCancelIndicator(theme: interfaceState.theme, strings: interfaceState.strings, cancel: { [weak self] in
|
||||||
self?.viewOnce = false
|
guard let self else {
|
||||||
self?.interfaceInteraction?.finishMediaRecording(.dismiss)
|
return
|
||||||
self?.tooltipController?.dismiss()
|
}
|
||||||
|
self.viewOnce = false
|
||||||
|
self.audioRecordingRemoveAnimationState = .recordingToAttachButton
|
||||||
|
self.interfaceInteraction?.finishMediaRecording(.dismiss)
|
||||||
|
self.tooltipController?.dismiss()
|
||||||
})
|
})
|
||||||
self.audioRecordingCancelIndicator = audioRecordingCancelIndicator
|
self.audioRecordingCancelIndicator = audioRecordingCancelIndicator
|
||||||
self.textInputContainerBackgroundView.contentView.addSubview(audioRecordingCancelIndicator)
|
self.textInputContainerBackgroundView.contentView.addSubview(audioRecordingCancelIndicator)
|
||||||
@ -2462,13 +2512,58 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
if let audioRecordingDotView = self.audioRecordingDotView {
|
if let audioRecordingDotView = self.audioRecordingDotView {
|
||||||
self.audioRecordingDotView = nil
|
self.audioRecordingDotView = nil
|
||||||
|
|
||||||
var dotFrame = audioRecordingDotView.bounds.size.centered(around: audioRecordingDotView.center)
|
if let audioRecordingRemoveAnimationState = self.audioRecordingRemoveAnimationState, case .recordingToAttachButton = audioRecordingRemoveAnimationState {
|
||||||
dotFrame.origin.x = hideOffset.x + leftInset + textFieldInsets.left + 16.0
|
self.audioRecordingRemoveAnimationState = nil
|
||||||
transition.updatePosition(layer: audioRecordingDotView.layer, position: dotFrame.center)
|
|
||||||
|
|
||||||
audioRecordingDotView.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, delay: 0.0, removeOnCompletion: false)
|
let sourceFrame = audioRecordingDotView.convert(audioRecordingDotView.bounds, to: self.attachmentButtonBackground.contentView)
|
||||||
audioRecordingDotView.layer.animateAlpha(from: CGFloat(audioRecordingDotView.layer.presentation()?.opacity ?? 1), to: 0.0, duration: 0.15, delay: 0.0, removeOnCompletion: false) { [weak audioRecordingDotView] _ in
|
audioRecordingDotView.removeFromSuperview()
|
||||||
audioRecordingDotView?.removeFromSuperview()
|
|
||||||
|
let dotAnimation = ComponentView<Empty>()
|
||||||
|
let dotAnimationSize = dotAnimation.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(name: "BinRed"),
|
||||||
|
color: UIColor(rgb: 0xFF3B30),
|
||||||
|
startingPosition: .begin
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||||
|
)
|
||||||
|
if let dotAnimationView = dotAnimation.view as? LottieComponent.View {
|
||||||
|
self.attachmentButtonBackground.contentView.addSubview(dotAnimationView)
|
||||||
|
dotAnimationView.frame = dotAnimationSize.centered(in: sourceFrame)
|
||||||
|
|
||||||
|
transition.updatePosition(layer: dotAnimationView.layer, position: self.attachmentButtonBackground.contentView.bounds.center)
|
||||||
|
|
||||||
|
self.attachmentButtonIcon.layer.opacity = 0.0
|
||||||
|
self.attachmentButtonIcon.layer.transform = CATransform3DMakeScale(0.001, 0.001, 1.0)
|
||||||
|
dotAnimationView.playOnce(completion: { [weak self, weak dotAnimationView] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
|
if let dotAnimationView {
|
||||||
|
transition.setAlpha(view: dotAnimationView, alpha: 0.0, completion: { [weak dotAnimationView] _ in
|
||||||
|
dotAnimationView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
transition.setScale(view: dotAnimationView, scale: 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setAlpha(view: self.attachmentButtonIcon, alpha: 1.0)
|
||||||
|
transition.setScale(view: self.attachmentButtonIcon, scale: 1.0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var dotFrame = audioRecordingDotView.bounds.size.centered(around: audioRecordingDotView.center)
|
||||||
|
dotFrame.origin.x = hideOffset.x + leftInset + textFieldInsets.left + 16.0
|
||||||
|
transition.updatePosition(layer: audioRecordingDotView.layer, position: dotFrame.center)
|
||||||
|
|
||||||
|
audioRecordingDotView.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, delay: 0.0, removeOnCompletion: false)
|
||||||
|
audioRecordingDotView.layer.animateAlpha(from: CGFloat(audioRecordingDotView.layer.presentation()?.opacity ?? 1), to: 0.0, duration: 0.15, delay: 0.0, removeOnCompletion: false) { [weak audioRecordingDotView] _ in
|
||||||
|
audioRecordingDotView?.removeFromSuperview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2861,7 +2956,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
transition.updateFrame(node: self.textPlaceholderNode, frame: textPlaceholderFrame)
|
transition.updateFrame(node: self.textPlaceholderNode, frame: textPlaceholderFrame)
|
||||||
|
|
||||||
let sendAsButtonFrame = CGRect(origin: CGPoint(x: 3.0, y: textInputContainerBackgroundFrame.height - 3.0 - 34.0), size: CGSize(width: 34.0, height: 34.0))
|
let sendAsButtonFrame = CGRect(origin: CGPoint(x: 3.0, y: textInputContainerBackgroundFrame.height - 3.0 - 34.0), size: CGSize(width: 34.0, height: 34.0))
|
||||||
transition.updateFrame(node: self.sendAsAvatarButtonNode, frame: sendAsButtonFrame)
|
transition.updatePosition(node: self.sendAsAvatarButtonNode, position: sendAsButtonFrame.center)
|
||||||
|
transition.updateBounds(node: self.sendAsAvatarButtonNode, bounds: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
|
||||||
|
transition.updateAlpha(layer: self.sendAsAvatarButtonNode.layer, alpha: audioRecordingItemsAlpha)
|
||||||
|
transition.updateTransformScale(layer: self.sendAsAvatarButtonNode.layer, scale: audioRecordingItemsAlpha == 0.0 ? 0.001 : 1.0)
|
||||||
transition.updateFrame(node: self.sendAsAvatarContainerNode, frame: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
|
transition.updateFrame(node: self.sendAsAvatarContainerNode, frame: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
|
||||||
transition.updateFrame(node: self.sendAsAvatarReferenceNode, frame: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
|
transition.updateFrame(node: self.sendAsAvatarReferenceNode, frame: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
|
||||||
transition.updatePosition(node: self.sendAsAvatarNode, position: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size).center)
|
transition.updatePosition(node: self.sendAsAvatarNode, position: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size).center)
|
||||||
@ -2905,7 +3003,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
var mediaActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - mediaActionButtonsSize.height), size: mediaActionButtonsSize)
|
var mediaActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - mediaActionButtonsSize.height), size: mediaActionButtonsSize)
|
||||||
if inputHasText || self.extendedSearchLayout || hasMediaDraft {
|
if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil {
|
||||||
mediaActionButtonsFrame.origin.x = width + 8.0
|
mediaActionButtonsFrame.origin.x = width + 8.0
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: self.mediaActionButtons, frame: mediaActionButtonsFrame)
|
transition.updateFrame(node: self.mediaActionButtons, frame: mediaActionButtonsFrame)
|
||||||
@ -4859,6 +4957,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
@objc func attachmentButtonPressed() {
|
@objc func attachmentButtonPressed() {
|
||||||
if let presentationInterfaceState = self.presentationInterfaceState, presentationInterfaceState.interfaceState.mediaDraftState != nil {
|
if let presentationInterfaceState = self.presentationInterfaceState, presentationInterfaceState.interfaceState.mediaDraftState != nil {
|
||||||
self.viewOnce = false
|
self.viewOnce = false
|
||||||
|
self.audioRecordingRemoveAnimationState = .previewToAttachButton
|
||||||
self.interfaceInteraction?.deleteRecordedMedia()
|
self.interfaceInteraction?.deleteRecordedMedia()
|
||||||
} else {
|
} else {
|
||||||
self.displayAttachmentMenu()
|
self.displayAttachmentMenu()
|
||||||
|
|||||||
24
submodules/TelegramUI/Components/GlassControls/BUILD
Normal file
24
submodules/TelegramUI/Components/GlassControls/BUILD
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "GlassControls",
|
||||||
|
module_name = "GlassControls",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,236 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ComponentFlow
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
import PlainButtonComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
|
||||||
|
public final class GlassControlGroupComponent: Component {
|
||||||
|
public final class Item: Equatable {
|
||||||
|
public enum Content: Hashable {
|
||||||
|
case icon(String)
|
||||||
|
case text(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
public let id: AnyHashable
|
||||||
|
public let content: Content
|
||||||
|
public let action: (() -> Void)?
|
||||||
|
|
||||||
|
public init(id: AnyHashable, content: Content, action: (() -> Void)?) {
|
||||||
|
self.id = id
|
||||||
|
self.content = content
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
|
if lhs.id != rhs.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.content != rhs.content {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (lhs.action == nil) != (rhs.action == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Background {
|
||||||
|
case panel
|
||||||
|
case activeTint
|
||||||
|
}
|
||||||
|
|
||||||
|
public let theme: PresentationTheme
|
||||||
|
public let background: Background
|
||||||
|
public let items: [Item]
|
||||||
|
public let minWidth: CGFloat
|
||||||
|
|
||||||
|
public init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
background: Background,
|
||||||
|
items: [Item],
|
||||||
|
minWidth: CGFloat
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.background = background
|
||||||
|
self.items = items
|
||||||
|
self.minWidth = minWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: GlassControlGroupComponent, rhs: GlassControlGroupComponent) -> Bool {
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.background != rhs.background {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.items != rhs.items {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.minWidth != rhs.minWidth {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let backgroundView: GlassBackgroundView
|
||||||
|
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
|
|
||||||
|
private var component: GlassControlGroupComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.backgroundView = GlassBackgroundView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func itemView(id: AnyHashable) -> UIView? {
|
||||||
|
return self.itemViews[id]?.view
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: GlassControlGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
struct ItemId: Hashable {
|
||||||
|
var id: AnyHashable
|
||||||
|
var contentId: AnyHashable
|
||||||
|
|
||||||
|
init(id: AnyHashable, contentId: AnyHashable) {
|
||||||
|
self.id = id
|
||||||
|
self.contentId = contentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentsWidth: CGFloat = 0.0
|
||||||
|
var validIds: [AnyHashable] = []
|
||||||
|
var isInteractive = false
|
||||||
|
for item in component.items {
|
||||||
|
let itemId = ItemId(id: item.id, contentId: item.content)
|
||||||
|
|
||||||
|
validIds.append(itemId)
|
||||||
|
|
||||||
|
let itemView: ComponentView<Empty>
|
||||||
|
var itemTransition = transition
|
||||||
|
if let current = self.itemViews[itemId] {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
itemView = ComponentView()
|
||||||
|
self.itemViews[itemId] = itemView
|
||||||
|
itemTransition = itemTransition.withAnimation(.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.action != nil {
|
||||||
|
isInteractive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let content: AnyComponent<Empty>
|
||||||
|
var itemInsets = UIEdgeInsets()
|
||||||
|
switch item.content {
|
||||||
|
case let .icon(name):
|
||||||
|
content = AnyComponent(BundleIconComponent(
|
||||||
|
name: name,
|
||||||
|
tintColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor
|
||||||
|
))
|
||||||
|
case let .text(string):
|
||||||
|
content = AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: string, font: Font.semibold(15.0), textColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor))
|
||||||
|
))
|
||||||
|
itemInsets.left = 10.0
|
||||||
|
itemInsets.right = itemInsets.left
|
||||||
|
}
|
||||||
|
|
||||||
|
var minItemWidth: CGFloat = 40.0
|
||||||
|
if component.items.count == 1 {
|
||||||
|
minItemWidth = max(minItemWidth, component.minWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemSize = itemView.update(
|
||||||
|
transition: itemTransition,
|
||||||
|
component: AnyComponent(PlainButtonComponent(
|
||||||
|
content: content,
|
||||||
|
minSize: CGSize(width: minItemWidth, height: 40.0),
|
||||||
|
contentInsets: itemInsets,
|
||||||
|
action: {
|
||||||
|
item.action?()
|
||||||
|
},
|
||||||
|
isEnabled: item.action != nil,
|
||||||
|
animateAlpha: false,
|
||||||
|
animateScale: false,
|
||||||
|
animateContents: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
|
)
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: contentsWidth, y: 0.0), size: itemSize)
|
||||||
|
|
||||||
|
if let itemComponentView = itemView.view {
|
||||||
|
var animateIn = false
|
||||||
|
if itemComponentView.superview == nil {
|
||||||
|
animateIn = true
|
||||||
|
self.backgroundView.contentView.addSubview(itemComponentView)
|
||||||
|
itemComponentView.alpha = 0.0
|
||||||
|
}
|
||||||
|
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||||
|
if animateIn {
|
||||||
|
alphaTransition.setAlpha(view: itemComponentView, alpha: 1.0)
|
||||||
|
alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 8.0, toRadius: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentsWidth += itemSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeIds: [AnyHashable] = []
|
||||||
|
for (id, itemView) in self.itemViews {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
if let itemComponentView = itemView.view {
|
||||||
|
alphaTransition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
|
||||||
|
itemComponentView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 0.0, toRadius: 8.0, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.itemViews.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: contentsWidth, height: availableSize.height)
|
||||||
|
let tintColor: GlassBackgroundView.TintColor
|
||||||
|
switch component.background {
|
||||||
|
case .panel:
|
||||||
|
tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
|
||||||
|
case .activeTint:
|
||||||
|
tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7), innerColor: component.theme.list.itemCheckColors.fillColor)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: isInteractive, transition: transition)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ComponentFlow
|
||||||
|
import GlassBackgroundComponent
|
||||||
|
|
||||||
|
public final class GlassControlPanelComponent: Component {
|
||||||
|
public final class Item: Equatable {
|
||||||
|
public let items: [GlassControlGroupComponent.Item]
|
||||||
|
public let background: GlassControlGroupComponent.Background
|
||||||
|
|
||||||
|
public init(items: [GlassControlGroupComponent.Item], background: GlassControlGroupComponent.Background) {
|
||||||
|
self.items = items
|
||||||
|
self.background = background
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
|
if lhs.items != rhs.items {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.background != rhs.background {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let theme: PresentationTheme
|
||||||
|
public let leftItem: Item?
|
||||||
|
public let rightItem: Item?
|
||||||
|
public let centralItem: Item?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
leftItem: Item?,
|
||||||
|
centralItem: Item?,
|
||||||
|
rightItem: Item?
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.leftItem = leftItem
|
||||||
|
self.centralItem = centralItem
|
||||||
|
self.rightItem = rightItem
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: GlassControlPanelComponent, rhs: GlassControlPanelComponent) -> Bool {
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.leftItem != rhs.leftItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.centralItem != rhs.centralItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.rightItem != rhs.rightItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let glassContainerView: GlassBackgroundContainerView
|
||||||
|
|
||||||
|
private var leftItemComponent: ComponentView<Empty>?
|
||||||
|
private var centralItemComponent: ComponentView<Empty>?
|
||||||
|
private var rightItemComponent: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var component: GlassControlPanelComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
public var leftItemView: GlassControlGroupComponent.View? {
|
||||||
|
return self.leftItemComponent?.view as? GlassControlGroupComponent.View
|
||||||
|
}
|
||||||
|
|
||||||
|
public var centerItemView: GlassControlGroupComponent.View? {
|
||||||
|
return self.centralItemComponent?.view as? GlassControlGroupComponent.View
|
||||||
|
}
|
||||||
|
|
||||||
|
public var rightItemView: GlassControlGroupComponent.View? {
|
||||||
|
return self.rightItemComponent?.view as? GlassControlGroupComponent.View
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.glassContainerView = GlassBackgroundContainerView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.glassContainerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: GlassControlPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||||
|
let minSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
|
var leftItemFrame: CGRect?
|
||||||
|
if let leftItem = component.leftItem {
|
||||||
|
let leftItemComponent: ComponentView<Empty>
|
||||||
|
var leftItemTransition = transition
|
||||||
|
if let current = self.leftItemComponent {
|
||||||
|
leftItemComponent = current
|
||||||
|
} else {
|
||||||
|
leftItemComponent = ComponentView()
|
||||||
|
self.leftItemComponent = leftItemComponent
|
||||||
|
leftItemTransition = transition.withAnimation(.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
let leftItemSize = leftItemComponent.update(
|
||||||
|
transition: leftItemTransition,
|
||||||
|
component: AnyComponent(GlassControlGroupComponent(
|
||||||
|
theme: component.theme,
|
||||||
|
background: leftItem.background,
|
||||||
|
items: leftItem.items,
|
||||||
|
minWidth: 40.0
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
|
)
|
||||||
|
let leftItemFrameValue = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: leftItemSize)
|
||||||
|
leftItemFrame = leftItemFrameValue
|
||||||
|
if let leftItemComponentView = leftItemComponent.view {
|
||||||
|
var animateIn = false
|
||||||
|
if leftItemComponentView.superview == nil {
|
||||||
|
animateIn = true
|
||||||
|
self.glassContainerView.contentView.addSubview(leftItemComponentView)
|
||||||
|
ComponentTransition.immediate.setScale(view: leftItemComponentView, scale: 0.001)
|
||||||
|
}
|
||||||
|
leftItemTransition.setPosition(view: leftItemComponentView, position: leftItemFrameValue.center)
|
||||||
|
leftItemTransition.setBounds(view: leftItemComponentView, bounds: CGRect(origin: CGPoint(), size: leftItemFrameValue.size))
|
||||||
|
if animateIn {
|
||||||
|
alphaTransition.animateAlpha(view: leftItemComponentView, from: 0.0, to: 1.0)
|
||||||
|
transition.setScale(view: leftItemComponentView, scale: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let leftItemComponent = self.leftItemComponent {
|
||||||
|
self.leftItemComponent = nil
|
||||||
|
if let leftItemComponentView = leftItemComponent.view {
|
||||||
|
transition.setScale(view: leftItemComponentView, scale: 0.001)
|
||||||
|
alphaTransition.setAlpha(view: leftItemComponentView, alpha: 0.0, completion: { [weak leftItemComponentView] _ in
|
||||||
|
leftItemComponentView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rightItemFrame: CGRect?
|
||||||
|
if let rightItem = component.rightItem {
|
||||||
|
let rightItemComponent: ComponentView<Empty>
|
||||||
|
var rightItemTransition = transition
|
||||||
|
if let current = self.rightItemComponent {
|
||||||
|
rightItemComponent = current
|
||||||
|
} else {
|
||||||
|
rightItemComponent = ComponentView()
|
||||||
|
self.rightItemComponent = rightItemComponent
|
||||||
|
rightItemTransition = transition.withAnimation(.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
let rightItemSize = rightItemComponent.update(
|
||||||
|
transition: rightItemTransition,
|
||||||
|
component: AnyComponent(GlassControlGroupComponent(
|
||||||
|
theme: component.theme,
|
||||||
|
background: rightItem.background,
|
||||||
|
items: rightItem.items,
|
||||||
|
minWidth: 40.0
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
|
)
|
||||||
|
let rightItemFrameValue = CGRect(origin: CGPoint(x: availableSize.width - rightItemSize.width, y: 0.0), size: rightItemSize)
|
||||||
|
rightItemFrame = rightItemFrameValue
|
||||||
|
if let rightItemComponentView = rightItemComponent.view {
|
||||||
|
var animateIn = false
|
||||||
|
if rightItemComponentView.superview == nil {
|
||||||
|
animateIn = true
|
||||||
|
self.glassContainerView.contentView.addSubview(rightItemComponentView)
|
||||||
|
ComponentTransition.immediate.setScale(view: rightItemComponentView, scale: 0.001)
|
||||||
|
}
|
||||||
|
rightItemTransition.setPosition(view: rightItemComponentView, position: rightItemFrameValue.center)
|
||||||
|
rightItemTransition.setBounds(view: rightItemComponentView, bounds: CGRect(origin: CGPoint(), size: rightItemFrameValue.size))
|
||||||
|
if animateIn {
|
||||||
|
alphaTransition.animateAlpha(view: rightItemComponentView, from: 0.0, to: 1.0)
|
||||||
|
transition.setScale(view: rightItemComponentView, scale: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let rightItemComponent = self.rightItemComponent {
|
||||||
|
self.rightItemComponent = nil
|
||||||
|
if let rightItemComponentView = rightItemComponent.view {
|
||||||
|
transition.setScale(view: rightItemComponentView, scale: 0.001)
|
||||||
|
alphaTransition.setAlpha(view: rightItemComponentView, alpha: 0.0, completion: { [weak rightItemComponentView] _ in
|
||||||
|
rightItemComponentView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let centralItem = component.centralItem {
|
||||||
|
let centralItemComponent: ComponentView<Empty>
|
||||||
|
var centralItemTransition = transition
|
||||||
|
if let current = self.centralItemComponent {
|
||||||
|
centralItemComponent = current
|
||||||
|
} else {
|
||||||
|
centralItemComponent = ComponentView()
|
||||||
|
self.centralItemComponent = centralItemComponent
|
||||||
|
centralItemTransition = transition.withAnimation(.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxCentralItemSize = CGSize(width: availableSize.width, height: availableSize.height)
|
||||||
|
var centralRightInset: CGFloat = 0.0
|
||||||
|
if let rightItemFrame {
|
||||||
|
centralRightInset = availableSize.width - rightItemFrame.minX + minSpacing
|
||||||
|
}
|
||||||
|
var centralLeftInset: CGFloat = 0.0
|
||||||
|
if let leftItemFrame {
|
||||||
|
centralLeftInset = leftItemFrame.maxX + minSpacing
|
||||||
|
}
|
||||||
|
maxCentralItemSize.width = max(1.0, availableSize.width - centralLeftInset - centralRightInset)
|
||||||
|
|
||||||
|
let centralItemSize = centralItemComponent.update(
|
||||||
|
transition: centralItemTransition,
|
||||||
|
component: AnyComponent(GlassControlGroupComponent(
|
||||||
|
theme: component.theme,
|
||||||
|
background: centralItem.background,
|
||||||
|
items: centralItem.items,
|
||||||
|
minWidth: 165.0
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: maxCentralItemSize
|
||||||
|
)
|
||||||
|
let centralItemFrameValue = CGRect(origin: CGPoint(x: centralLeftInset + floor((availableSize.width - centralLeftInset - centralRightInset - centralItemSize.width) * 0.5), y: 0.0), size: centralItemSize)
|
||||||
|
if let centralItemComponentView = centralItemComponent.view {
|
||||||
|
var animateIn = false
|
||||||
|
if centralItemComponentView.superview == nil {
|
||||||
|
animateIn = true
|
||||||
|
self.glassContainerView.contentView.addSubview(centralItemComponentView)
|
||||||
|
ComponentTransition.immediate.setScale(view: centralItemComponentView, scale: 0.001)
|
||||||
|
}
|
||||||
|
centralItemTransition.setPosition(view: centralItemComponentView, position: centralItemFrameValue.center)
|
||||||
|
centralItemTransition.setBounds(view: centralItemComponentView, bounds: CGRect(origin: CGPoint(), size: centralItemFrameValue.size))
|
||||||
|
if animateIn {
|
||||||
|
alphaTransition.animateAlpha(view: centralItemComponentView, from: 0.0, to: 1.0)
|
||||||
|
transition.setScale(view: centralItemComponentView, scale: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let centralItemComponent = self.centralItemComponent {
|
||||||
|
self.centralItemComponent = nil
|
||||||
|
if let centralItemComponentView = centralItemComponent.view {
|
||||||
|
transition.setScale(view: centralItemComponentView, scale: 0.001)
|
||||||
|
alphaTransition.setAlpha(view: centralItemComponentView, alpha: 0.0, completion: { [weak centralItemComponentView] _ in
|
||||||
|
centralItemComponentView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.glassContainerView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
self.glassContainerView.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -331,9 +331,15 @@ public final class ListSectionComponent: Component {
|
|||||||
case legacy
|
case legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum BackgroundColor {
|
||||||
|
case base
|
||||||
|
case modal
|
||||||
|
}
|
||||||
|
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let style: Style
|
public let style: Style
|
||||||
public let background: Background
|
public let background: Background
|
||||||
|
public let backgroundColor: BackgroundColor
|
||||||
public let header: AnyComponent<Empty>?
|
public let header: AnyComponent<Empty>?
|
||||||
public let footer: AnyComponent<Empty>?
|
public let footer: AnyComponent<Empty>?
|
||||||
public let items: [AnyComponentWithIdentity<Empty>]
|
public let items: [AnyComponentWithIdentity<Empty>]
|
||||||
@ -345,6 +351,7 @@ public final class ListSectionComponent: Component {
|
|||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
style: Style = .legacy,
|
style: Style = .legacy,
|
||||||
background: Background = .all,
|
background: Background = .all,
|
||||||
|
backgroundColor: BackgroundColor = .base,
|
||||||
header: AnyComponent<Empty>?,
|
header: AnyComponent<Empty>?,
|
||||||
footer: AnyComponent<Empty>?,
|
footer: AnyComponent<Empty>?,
|
||||||
items: [AnyComponentWithIdentity<Empty>],
|
items: [AnyComponentWithIdentity<Empty>],
|
||||||
@ -355,6 +362,7 @@ public final class ListSectionComponent: Component {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.style = style
|
self.style = style
|
||||||
self.background = background
|
self.background = background
|
||||||
|
self.backgroundColor = backgroundColor
|
||||||
self.header = header
|
self.header = header
|
||||||
self.footer = footer
|
self.footer = footer
|
||||||
self.items = items
|
self.items = items
|
||||||
@ -373,6 +381,9 @@ public final class ListSectionComponent: Component {
|
|||||||
if lhs.background != rhs.background {
|
if lhs.background != rhs.background {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.backgroundColor != rhs.backgroundColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.header != rhs.header {
|
if lhs.header != rhs.header {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -182,6 +182,16 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct StarStats: Equatable {
|
||||||
|
public var myStars: Int64
|
||||||
|
public var totalStars: Int64
|
||||||
|
|
||||||
|
public init(myStars: Int64, totalStars: Int64) {
|
||||||
|
self.myStars = myStars
|
||||||
|
self.totalStars = totalStars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let externalState: ExternalState
|
public let externalState: ExternalState
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
@ -244,6 +254,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
public let liveChatState: LiveChatState?
|
public let liveChatState: LiveChatState?
|
||||||
public let toggleLiveChatExpanded: (() -> Void)?
|
public let toggleLiveChatExpanded: (() -> Void)?
|
||||||
public let sendStarsAction: ((UIView, Bool) -> Void)?
|
public let sendStarsAction: ((UIView, Bool) -> Void)?
|
||||||
|
public let starStars: StarStats?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
externalState: ExternalState,
|
externalState: ExternalState,
|
||||||
@ -307,7 +318,8 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
chatLocation: ChatLocation?,
|
chatLocation: ChatLocation?,
|
||||||
liveChatState: LiveChatState? = nil,
|
liveChatState: LiveChatState? = nil,
|
||||||
toggleLiveChatExpanded: (() -> Void)? = nil,
|
toggleLiveChatExpanded: (() -> Void)? = nil,
|
||||||
sendStarsAction: ((UIView, Bool) -> Void)? = nil
|
sendStarsAction: ((UIView, Bool) -> Void)? = nil,
|
||||||
|
starStars: StarStats? = nil
|
||||||
) {
|
) {
|
||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -371,6 +383,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
self.liveChatState = liveChatState
|
self.liveChatState = liveChatState
|
||||||
self.toggleLiveChatExpanded = toggleLiveChatExpanded
|
self.toggleLiveChatExpanded = toggleLiveChatExpanded
|
||||||
self.sendStarsAction = sendStarsAction
|
self.sendStarsAction = sendStarsAction
|
||||||
|
self.starStars = starStars
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool {
|
public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool {
|
||||||
@ -503,6 +516,9 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
if lhs.liveChatState != rhs.liveChatState {
|
if lhs.liveChatState != rhs.liveChatState {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.starStars != rhs.starStars {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -967,7 +983,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
component.toggleLiveChatExpanded?()
|
component.toggleLiveChatExpanded?()
|
||||||
}),
|
}),
|
||||||
rightAction: ChatTextInputPanelComponent.RightAction(kind: .stars(count: Int(component.storyItem?.views?.reactions.first(where: { $0.value == .stars })?.count ?? 0), isFilled: component.myReaction?.reaction == .stars), action: { [weak self] sourceView in
|
rightAction: ChatTextInputPanelComponent.RightAction(kind: .stars(count: Int(component.starStars?.totalStars ?? 0), isFilled: (component.starStars?.myStars ?? 0) != 0), action: { [weak self] sourceView in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,6 +108,8 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
"//submodules/TelegramUI/Components/Stories/LiveChat/StoryLiveChatMessageComponent",
|
"//submodules/TelegramUI/Components/Stories/LiveChat/StoryLiveChatMessageComponent",
|
||||||
"//submodules/TelegramUI/Components/StarsParticleEffect",
|
"//submodules/TelegramUI/Components/StarsParticleEffect",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/AdminUserActionsSheet",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -1945,7 +1945,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
size: availableSize,
|
size: availableSize,
|
||||||
metrics: environment.metrics,
|
metrics: environment.metrics,
|
||||||
deviceMetrics: environment.deviceMetrics,
|
deviceMetrics: environment.deviceMetrics,
|
||||||
intrinsicInsets: UIEdgeInsets(top: environment.statusBarHeight, left: 0.0, bottom: contentDerivedBottomInset + presentationContextInsets.bottom, right: 0.0),
|
intrinsicInsets: UIEdgeInsets(top: environment.statusBarHeight + 54.0, left: 0.0, bottom: contentDerivedBottomInset + presentationContextInsets.bottom, right: 0.0),
|
||||||
safeInsets: UIEdgeInsets(top: 0.0, left: presentationContextInsets.left, bottom: 0.0, right: presentationContextInsets.right),
|
safeInsets: UIEdgeInsets(top: 0.0, left: presentationContextInsets.left, bottom: 0.0, right: presentationContextInsets.right),
|
||||||
additionalInsets: UIEdgeInsets(),
|
additionalInsets: UIEdgeInsets(),
|
||||||
statusBarHeight: nil,
|
statusBarHeight: nil,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import MultilineTextComponent
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import StarsParticleEffect
|
import StarsParticleEffect
|
||||||
import StoryLiveChatMessageComponent
|
import StoryLiveChatMessageComponent
|
||||||
|
import AdminUserActionsSheet
|
||||||
|
|
||||||
private final class PinnedBarMessageComponent: Component {
|
private final class PinnedBarMessageComponent: Component {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -374,6 +375,7 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
let call: PresentationGroupCall
|
let call: PresentationGroupCall
|
||||||
let storyPeerId: EnginePeer.Id
|
let storyPeerId: EnginePeer.Id
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
|
let controller: () -> ViewController?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
external: External,
|
external: External,
|
||||||
@ -382,7 +384,8 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
call: PresentationGroupCall,
|
call: PresentationGroupCall,
|
||||||
storyPeerId: EnginePeer.Id,
|
storyPeerId: EnginePeer.Id,
|
||||||
insets: UIEdgeInsets
|
insets: UIEdgeInsets,
|
||||||
|
controller: @escaping () -> ViewController?
|
||||||
) {
|
) {
|
||||||
self.external = external
|
self.external = external
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -391,6 +394,7 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
self.call = call
|
self.call = call
|
||||||
self.storyPeerId = storyPeerId
|
self.storyPeerId = storyPeerId
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
|
self.controller = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StoryContentLiveChatComponent, rhs: StoryContentLiveChatComponent) -> Bool {
|
static func ==(lhs: StoryContentLiveChatComponent, rhs: StoryContentLiveChatComponent) -> Bool {
|
||||||
@ -437,6 +441,7 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
private var stateDisposable: Disposable?
|
private var stateDisposable: Disposable?
|
||||||
|
|
||||||
private var currentListIsEmpty: Bool = true
|
private var currentListIsEmpty: Bool = true
|
||||||
|
private var isMessageContextMenuOpen: Bool = false
|
||||||
|
|
||||||
public var isChatEmpty: Bool {
|
public var isChatEmpty: Bool {
|
||||||
guard let messagesState = self.messagesState else {
|
guard let messagesState = self.messagesState else {
|
||||||
@ -446,6 +451,17 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
}
|
}
|
||||||
private(set) var isChatExpanded: Bool = false
|
private(set) var isChatExpanded: Bool = false
|
||||||
|
|
||||||
|
public var starStars: (myStars: Int64, pendingMyStars: Int64, totalStars: Int64, topItems: [GroupCallMessagesContext.TopStarsItem])? {
|
||||||
|
guard let messagesState = self.messagesState else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var myStars: Int64 = 0
|
||||||
|
if let item = messagesState.topStars.first(where: { $0.isMy }) {
|
||||||
|
myStars = item.amount
|
||||||
|
}
|
||||||
|
return (myStars + messagesState.pendingMyStars, pendingMyStars: messagesState.pendingMyStars, messagesState.totalStars + messagesState.pendingMyStars, messagesState.topStars)
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.listContainer = UIView()
|
self.listContainer = UIView()
|
||||||
|
|
||||||
@ -506,7 +522,7 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
self.addSubview(self.listShadowView)
|
self.addSubview(self.listShadowView)
|
||||||
self.addSubview(self.listContainer)
|
self.addSubview(self.listContainer)
|
||||||
|
|
||||||
//self.isChatExpanded = true
|
self.isChatExpanded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -540,6 +556,60 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func displayDeleteMessageAndBan(id: GroupCallMessagesContext.Message.Id) {
|
||||||
|
Task { @MainActor [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let chatPeer = await component.context.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.Peer(id: component.storyPeerId)
|
||||||
|
).get() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let messagesState = self.messagesState, let message = messagesState.messages.first(where: { $0.id == id }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let author = message.author else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var totalCount = 0
|
||||||
|
for message in messagesState.messages {
|
||||||
|
if message.author?.id == author.id {
|
||||||
|
totalCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let controller = component.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.push(AdminUserActionsSheet(
|
||||||
|
context: component.context,
|
||||||
|
chatPeer: chatPeer,
|
||||||
|
peers: [RenderedChannelParticipant(
|
||||||
|
participant: .member(
|
||||||
|
id: author.id,
|
||||||
|
invitedAt: 0,
|
||||||
|
adminInfo: nil,
|
||||||
|
banInfo: nil,
|
||||||
|
rank: nil,
|
||||||
|
subscriptionUntilDate: nil
|
||||||
|
),
|
||||||
|
peer: author._asPeer()
|
||||||
|
)],
|
||||||
|
mode: .liveStream(
|
||||||
|
messageCount: 1,
|
||||||
|
deleteAllMessageCount: totalCount,
|
||||||
|
completion: { [weak self] result in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = self
|
||||||
|
}
|
||||||
|
),
|
||||||
|
customTheme: defaultDarkColorPresentationTheme
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func openMessageContextMenu(id: GroupCallMessagesContext.Message.Id, gesture: ContextGesture, sourceNode: ContextExtractedContentContainingNode) {
|
private func openMessageContextMenu(id: GroupCallMessagesContext.Message.Id, gesture: ContextGesture, sourceNode: ContextExtractedContentContainingNode) {
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -572,7 +642,52 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
})))
|
})))
|
||||||
|
|
||||||
let state = await (component.call.state |> take(1)).get()
|
let state = await (component.call.state |> take(1)).get()
|
||||||
if state.canManageCall || component.storyPeerId == component.context.account.peerId {
|
|
||||||
|
var isAdmin = state.canManageCall
|
||||||
|
if component.storyPeerId == component.context.account.peerId {
|
||||||
|
isAdmin = true
|
||||||
|
}
|
||||||
|
var canDelete = isAdmin
|
||||||
|
var isMyMessage = false
|
||||||
|
guard let messagesState = self.messagesState, let message = messagesState.messages.first(where: { $0.id == id }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if message.author?.id == component.context.account.peerId {
|
||||||
|
isMyMessage = true
|
||||||
|
canDelete = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isMyMessage, let author = message.author {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Open Profile", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c?.dismiss(completion: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||||
|
navigationController: navigationController,
|
||||||
|
context: component.context,
|
||||||
|
chatLocation: .peer(author),
|
||||||
|
keepStack: .always
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if "".isEmpty {
|
||||||
|
isAdmin = true
|
||||||
|
canDelete = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if canDelete {
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -583,7 +698,11 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let call = component.call as? PresentationGroupCallImpl {
|
if let call = component.call as? PresentationGroupCallImpl {
|
||||||
call.deleteMessage(id: id)
|
if isAdmin && !isMyMessage {
|
||||||
|
self.displayDeleteMessageAndBan(id: id)
|
||||||
|
} else {
|
||||||
|
call.deleteMessage(id: id, reportSpam: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
@ -604,17 +723,18 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let listView = self.list.view {
|
self.isMessageContextMenuOpen = false
|
||||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
if !self.isUpdating {
|
||||||
transition.setAlpha(view: listView, alpha: 1.0)
|
self.state?.updated(transition: .easeInOut(duration: 0.2), isLocal: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let listView = self.list.view {
|
|
||||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
self.isMessageContextMenuOpen = true
|
||||||
transition.setAlpha(view: listView, alpha: 0.25)
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.2), isLocal: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
component.context.sharedContext.mainWindow?.presentInGlobalOverlay(contextController)
|
component.controller()?.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,7 +902,14 @@ final class StoryContentLiveChatComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setPosition(view: listView, position: listFrame.offsetBy(dx: 0.0, dy: self.isChatExpanded ? 0.0 : listFrame.height).center)
|
transition.setPosition(view: listView, position: listFrame.offsetBy(dx: 0.0, dy: self.isChatExpanded ? 0.0 : listFrame.height).center)
|
||||||
transition.setBounds(view: listView, bounds: CGRect(origin: CGPoint(), size: listFrame.size))
|
transition.setBounds(view: listView, bounds: CGRect(origin: CGPoint(), size: listFrame.size))
|
||||||
alphaTransition.setAlpha(view: listView, alpha: listItems.isEmpty ? 0.0 : 1.0)
|
|
||||||
|
let listAlpha: CGFloat
|
||||||
|
if self.isMessageContextMenuOpen {
|
||||||
|
listAlpha = 0.25
|
||||||
|
} else {
|
||||||
|
listAlpha = listItems.isEmpty ? 0.0 : 1.0
|
||||||
|
}
|
||||||
|
alphaTransition.setAlpha(view: listView, alpha: listAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.listContainer, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: self.listContainer, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
|||||||
@ -40,8 +40,9 @@ final class StoryItemContentComponent: Component {
|
|||||||
let preferHighQuality: Bool
|
let preferHighQuality: Bool
|
||||||
let isEmbeddedInCamera: Bool
|
let isEmbeddedInCamera: Bool
|
||||||
let activateReaction: (UIView, MessageReaction.Reaction) -> Void
|
let activateReaction: (UIView, MessageReaction.Reaction) -> Void
|
||||||
|
let controller: () -> ViewController?
|
||||||
|
|
||||||
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) {
|
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void, controller: @escaping () -> ViewController?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
@ -55,6 +56,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
self.preferHighQuality = preferHighQuality
|
self.preferHighQuality = preferHighQuality
|
||||||
self.isEmbeddedInCamera = isEmbeddedInCamera
|
self.isEmbeddedInCamera = isEmbeddedInCamera
|
||||||
self.activateReaction = activateReaction
|
self.activateReaction = activateReaction
|
||||||
|
self.controller = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
||||||
@ -167,6 +169,13 @@ final class StoryItemContentComponent: Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var starStars: (myStars: Int64, pendingMyStars: Int64, totalStars: Int64, topItems: [GroupCallMessagesContext.TopStarsItem])? {
|
||||||
|
guard let liveChatView = self.liveChat?.view as? StoryContentLiveChatComponent.View else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return liveChatView.starStars
|
||||||
|
}
|
||||||
|
|
||||||
public func toggleLiveChatExpanded() {
|
public func toggleLiveChatExpanded() {
|
||||||
guard let liveChatView = self.liveChat?.view as? StoryContentLiveChatComponent.View else {
|
guard let liveChatView = self.liveChat?.view as? StoryContentLiveChatComponent.View else {
|
||||||
return
|
return
|
||||||
@ -852,7 +861,13 @@ final class StoryItemContentComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
call: mediaStreamCall,
|
call: mediaStreamCall,
|
||||||
storyPeerId: component.peer.id,
|
storyPeerId: component.peer.id,
|
||||||
insets: environment.containerInsets
|
insets: environment.containerInsets,
|
||||||
|
controller: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return component.controller()
|
||||||
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
|
|||||||
@ -1628,6 +1628,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.sendMessageContext.activateInlineReaction(view: self, reactionView: reactionView, reaction: reaction)
|
self.sendMessageContext.activateInlineReaction(view: self, reactionView: reactionView, reaction: reaction)
|
||||||
|
},
|
||||||
|
controller: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return component.controller()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {
|
environment: {
|
||||||
@ -2964,6 +2970,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var liveChatState: MessageInputPanelComponent.LiveChatState?
|
var liveChatState: MessageInputPanelComponent.LiveChatState?
|
||||||
|
var starStats: MessageInputPanelComponent.StarStats?
|
||||||
if let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
|
if let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
|
||||||
liveChatState = visibleItemView.liveChatState.flatMap { liveChatState in
|
liveChatState = visibleItemView.liveChatState.flatMap { liveChatState in
|
||||||
return MessageInputPanelComponent.LiveChatState(
|
return MessageInputPanelComponent.LiveChatState(
|
||||||
@ -2971,6 +2978,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
hasUnseenMessages: liveChatState.hasUnseenMessages
|
hasUnseenMessages: liveChatState.hasUnseenMessages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
starStats = visibleItemView.starStars.flatMap { starStats in
|
||||||
|
return MessageInputPanelComponent.StarStats(
|
||||||
|
myStars: starStats.myStars,
|
||||||
|
totalStars: starStats.totalStars
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputPanelSize = self.inputPanel.update(
|
inputPanelSize = self.inputPanel.update(
|
||||||
@ -3220,7 +3233,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
self.sendMessageContext.performSendStars(view: self, buttonView: sourceView, count: 1, isFromExpandedView: false)
|
self.sendMessageContext.performSendStars(view: self, buttonView: sourceView, count: 1, isFromExpandedView: false)
|
||||||
}
|
}
|
||||||
} : nil
|
} : nil,
|
||||||
|
starStars: starStats
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)
|
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)
|
||||||
|
|||||||
@ -52,6 +52,7 @@ import StoryQualityUpgradeSheetScreen
|
|||||||
import AudioWaveform
|
import AudioWaveform
|
||||||
import ChatMessagePaymentAlertController
|
import ChatMessagePaymentAlertController
|
||||||
import ChatSendStarsScreen
|
import ChatSendStarsScreen
|
||||||
|
import AnimatedTextComponent
|
||||||
|
|
||||||
private var ObjCKey_DeinitWatcher: Int?
|
private var ObjCKey_DeinitWatcher: Int?
|
||||||
|
|
||||||
@ -102,6 +103,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
var currentSpeechHolder: SpeechSynthesizerHolder?
|
var currentSpeechHolder: SpeechSynthesizerHolder?
|
||||||
|
|
||||||
var currentLiveStreamMessageStars: StarsAmount?
|
var currentLiveStreamMessageStars: StarsAmount?
|
||||||
|
weak var currentSendStarsUndoController: UndoOverlayController?
|
||||||
|
|
||||||
private(set) var isMediaRecordingLocked: Bool = false
|
private(set) var isMediaRecordingLocked: Bool = false
|
||||||
var wasRecordingDismissed: Bool = false
|
var wasRecordingDismissed: Bool = false
|
||||||
@ -422,7 +424,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.currentLiveStreamMessageStars = nil
|
self.currentLiveStreamMessageStars = nil
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
view.state?.updated(transition: .spring(duration: 0.4))
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -677,7 +679,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
self.currentInputMode = .text
|
self.currentInputMode = .text
|
||||||
self.currentLiveStreamMessageStars = nil
|
self.currentLiveStreamMessageStars = nil
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
view.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
|
||||||
let controller = component.controller() as? StoryContainerScreen
|
let controller = component.controller() as? StoryContainerScreen
|
||||||
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
@ -759,7 +761,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
if hasFirstResponder(view) {
|
if hasFirstResponder(view) {
|
||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
} else {
|
} else {
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
view.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
@ -826,7 +828,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
if hasFirstResponder(view) {
|
if hasFirstResponder(view) {
|
||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
} else {
|
} else {
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
view.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
})
|
})
|
||||||
@ -886,7 +888,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
if hasFirstResponder(view) {
|
if hasFirstResponder(view) {
|
||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
} else {
|
} else {
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
view.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
})
|
})
|
||||||
@ -3890,11 +3892,26 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var topPeers: [ReactionsMessageAttribute.TopPeer] = []
|
||||||
|
if let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
|
||||||
|
if let topItems = visibleItemView.starStars?.topItems {
|
||||||
|
topPeers = topItems.map { item -> ReactionsMessageAttribute.TopPeer in
|
||||||
|
return ReactionsMessageAttribute.TopPeer(
|
||||||
|
peerId: item.peerId,
|
||||||
|
count: Int32(item.amount),
|
||||||
|
isTop: item.isTop,
|
||||||
|
isMy: item.isMy,
|
||||||
|
isAnonymous: item.isAnonymous
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let initialData = await ChatSendStarsScreen.initialData(
|
let initialData = await ChatSendStarsScreen.initialData(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id),
|
reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id),
|
||||||
topPeers: [],
|
topPeers: topPeers,
|
||||||
completion: { [weak view] amount, privacy, isBecomingTop, transitionOut in
|
completion: { [weak view] amount, privacy, isBecomingTop, transitionOut in
|
||||||
guard let view, let component = view.component else {
|
guard let view, let component = view.component else {
|
||||||
return
|
return
|
||||||
@ -3916,7 +3933,168 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = component.context.engine.messages.sendStoryStars(peerId: component.slice.effectivePeer.id, id: component.slice.item.storyItem.id, count: count).startStandalone()
|
if isFromExpandedView {
|
||||||
|
self.commitSendStars(view: view, count: count, delay: false)
|
||||||
|
} else {
|
||||||
|
Task { @MainActor [weak view] in
|
||||||
|
guard let view, let component = view.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var reactionItem: ReactionItem?
|
||||||
|
if let availableReactions = await component.context.availableReactions.get() {
|
||||||
|
for item in availableReactions.reactions {
|
||||||
|
if item.value == .stars {
|
||||||
|
guard let centerAnimation = item.centerAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
guard let aroundAnimation = item.aroundAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reactionItem = ReactionItem(
|
||||||
|
reaction: ReactionItem.Reaction(rawValue: item.value),
|
||||||
|
appearAnimation: item.appearAnimation,
|
||||||
|
stillAnimation: item.selectAnimation,
|
||||||
|
listAnimation: centerAnimation,
|
||||||
|
largeListAnimation: item.activateAnimation,
|
||||||
|
applicationAnimation: aroundAnimation,
|
||||||
|
largeApplicationAnimation: item.effectAnimation,
|
||||||
|
isCustom: false
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let reactionItem {
|
||||||
|
let targetFrame = buttonView.convert(buttonView.bounds, to: view)
|
||||||
|
|
||||||
|
let targetView = UIView(frame: targetFrame)
|
||||||
|
targetView.isUserInteractionEnabled = false
|
||||||
|
view.addSubview(targetView)
|
||||||
|
|
||||||
|
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false)
|
||||||
|
view.componentContainerView.addSubview(standaloneReactionAnimation.view)
|
||||||
|
|
||||||
|
if let standaloneReactionAnimation = view.standaloneReactionAnimation {
|
||||||
|
view.standaloneReactionAnimation = nil
|
||||||
|
|
||||||
|
let standaloneReactionAnimationView = standaloneReactionAnimation.view
|
||||||
|
standaloneReactionAnimation.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak standaloneReactionAnimationView] _ in
|
||||||
|
standaloneReactionAnimationView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
view.standaloneReactionAnimation = standaloneReactionAnimation
|
||||||
|
|
||||||
|
standaloneReactionAnimation.frame = view.bounds
|
||||||
|
standaloneReactionAnimation.animateReactionSelection(
|
||||||
|
context: component.context,
|
||||||
|
theme: component.theme,
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
reaction: reactionItem,
|
||||||
|
avatarPeers: [],
|
||||||
|
playHaptic: true,
|
||||||
|
isLarge: false,
|
||||||
|
hideCenterAnimation: true,
|
||||||
|
targetView: targetView,
|
||||||
|
addStandaloneReactionAnimation: { [weak view] standaloneReactionAnimation in
|
||||||
|
guard let view else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let standaloneReactionAnimation = view.standaloneReactionAnimation {
|
||||||
|
view.standaloneReactionAnimation = nil
|
||||||
|
standaloneReactionAnimation.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
view.standaloneReactionAnimation = standaloneReactionAnimation
|
||||||
|
|
||||||
|
standaloneReactionAnimation.frame = view.bounds
|
||||||
|
view.componentContainerView.addSubview(standaloneReactionAnimation.view)
|
||||||
|
},
|
||||||
|
completion: { [weak targetView, weak standaloneReactionAnimation] in
|
||||||
|
targetView?.removeFromSuperview()
|
||||||
|
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.commitSendStars(view: view, count: count, delay: true)
|
||||||
|
|
||||||
|
var totalStars = count
|
||||||
|
if let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
|
||||||
|
if let pendingMyStars = visibleItemView.starStars?.pendingMyStars {
|
||||||
|
totalStars += Int(pendingMyStars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
/*if case .anonymous = privacy {
|
||||||
|
title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount))
|
||||||
|
} else if case .peer = privacy, let privacyPeer {
|
||||||
|
let rawTitle = self.presentationData.strings.Chat_ToastStarsSent_TitleChannel(Int32(self.currentSendStarsUndoCount))
|
||||||
|
title = rawTitle.replacingOccurrences(of: "{name}", with: privacyPeer.compactDisplayTitle)
|
||||||
|
} else*/ do {
|
||||||
|
title = component.strings.Chat_ToastStarsSent_Title(Int32(totalStars))
|
||||||
|
}
|
||||||
|
|
||||||
|
let textItems = AnimatedTextComponent.extractAnimatedTextString(string: component.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [
|
||||||
|
0: .number(totalStars, minDigits: 1),
|
||||||
|
1: .text(component.strings.Chat_ToastStarsSent_TextStarAmount(Int32(totalStars)))
|
||||||
|
])
|
||||||
|
|
||||||
|
if let current = self.currentSendStarsUndoController {
|
||||||
|
current.content = .starsSent(context: component.context, title: title, text: textItems, hasUndo: true)
|
||||||
|
} else {
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||||
|
let controller = UndoOverlayController(presentationData: presentationData, content: .starsSent(context: component.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak view] action in
|
||||||
|
guard let view else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .undo = action {
|
||||||
|
guard let component = view.component else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard case .liveStream = component.slice.item.storyItem.media else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let visibleItem = view.visibleItems[component.slice.item.id], let itemView = visibleItem.view.view as? StoryItemContentComponent.View else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let call = itemView.mediaStreamCall else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
call.cancelSendStars()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
self.currentSendStarsUndoController = controller
|
||||||
|
self.view?.component?.controller()?.present(controller, in: .current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func commitSendStars(view: StoryItemSetContainerComponent.View, count: Int, delay: Bool) {
|
||||||
|
guard let component = view.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .liveStream = component.slice.item.storyItem.media else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let visibleItem = view.visibleItems[component.slice.item.id], let itemView = visibleItem.view.view as? StoryItemContentComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let call = itemView.mediaStreamCall else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let current = self.currentSendStarsUndoController {
|
||||||
|
self.currentSendStarsUndoController = nil
|
||||||
|
current.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
call.sendStars(amount: Int64(count), delay: delay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user