mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into post-suggestion
This commit is contained in:
commit
d6334b9748
43
.vscode/launch.json
vendored
43
.vscode/launch.json
vendored
@ -5,15 +5,40 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "sweetpad-lldb",
|
"type": "swift",
|
||||||
"request": "attach",
|
"request": "launch",
|
||||||
"name": "Attach to running app (SweetPad)",
|
"args": [],
|
||||||
"preLaunchTask": "sweetpad: launch",
|
"cwd": "${workspaceFolder:telegram-ios}",
|
||||||
"codelldbAttributes": {
|
"name": "Debug Telegram",
|
||||||
"initCommands": [
|
"program": "${workspaceFolder:telegram-ios}/.build/debug/Telegram",
|
||||||
"command source ~/.lldbinit-Xcode"
|
"preLaunchTask": "swift: Build Debug Telegram"
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
|
"type": "swift",
|
||||||
|
"request": "launch",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder:telegram-ios}",
|
||||||
|
"name": "Release Telegram",
|
||||||
|
"program": "${workspaceFolder:telegram-ios}/.build/release/Telegram",
|
||||||
|
"preLaunchTask": "swift: Build Release Telegram"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "swift",
|
||||||
|
"request": "launch",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder:telegram-ios}",
|
||||||
|
"name": "Debug telegram-ios",
|
||||||
|
"program": "${workspaceFolder:telegram-ios}/.build/debug/telegram-ios",
|
||||||
|
"preLaunchTask": "swift: Build Debug telegram-ios"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "swift",
|
||||||
|
"request": "launch",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder:telegram-ios}",
|
||||||
|
"name": "Release telegram-ios",
|
||||||
|
"program": "${workspaceFolder:telegram-ios}/.build/release/telegram-ios",
|
||||||
|
"preLaunchTask": "swift: Build Release telegram-ios"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
84
.vscode/settings.json
vendored
84
.vscode/settings.json
vendored
@ -2,78 +2,14 @@
|
|||||||
"sweetpad.build.xcodeWorkspacePath": "Telegram/Telegram.xcodeproj/project.xcworkspace",
|
"sweetpad.build.xcodeWorkspacePath": "Telegram/Telegram.xcodeproj/project.xcworkspace",
|
||||||
"lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB",
|
"lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB",
|
||||||
"lldb.launch.expressions": "native",
|
"lldb.launch.expressions": "native",
|
||||||
"files.associations": {
|
"search.followSymlinks": false,
|
||||||
"__bit_reference": "cpp",
|
"files.exclude": {
|
||||||
"__hash_table": "cpp",
|
".git/**": true
|
||||||
"__locale": "cpp",
|
},
|
||||||
"__node_handle": "cpp",
|
"files.watcherExclude": {
|
||||||
"__split_buffer": "cpp",
|
".git/**": true
|
||||||
"__threading_support": "cpp",
|
},
|
||||||
"__tree": "cpp",
|
"search.exclude": {
|
||||||
"__verbose_abort": "cpp",
|
".git/**": true
|
||||||
"any": "cpp",
|
|
||||||
"array": "cpp",
|
|
||||||
"bitset": "cpp",
|
|
||||||
"cctype": "cpp",
|
|
||||||
"cfenv": "cpp",
|
|
||||||
"charconv": "cpp",
|
|
||||||
"cinttypes": "cpp",
|
|
||||||
"clocale": "cpp",
|
|
||||||
"cmath": "cpp",
|
|
||||||
"codecvt": "cpp",
|
|
||||||
"complex": "cpp",
|
|
||||||
"condition_variable": "cpp",
|
|
||||||
"csignal": "cpp",
|
|
||||||
"cstdarg": "cpp",
|
|
||||||
"cstddef": "cpp",
|
|
||||||
"cstdint": "cpp",
|
|
||||||
"cstdio": "cpp",
|
|
||||||
"cstdlib": "cpp",
|
|
||||||
"cstring": "cpp",
|
|
||||||
"ctime": "cpp",
|
|
||||||
"cwchar": "cpp",
|
|
||||||
"cwctype": "cpp",
|
|
||||||
"deque": "cpp",
|
|
||||||
"execution": "cpp",
|
|
||||||
"memory": "cpp",
|
|
||||||
"forward_list": "cpp",
|
|
||||||
"fstream": "cpp",
|
|
||||||
"future": "cpp",
|
|
||||||
"initializer_list": "cpp",
|
|
||||||
"iomanip": "cpp",
|
|
||||||
"ios": "cpp",
|
|
||||||
"iosfwd": "cpp",
|
|
||||||
"iostream": "cpp",
|
|
||||||
"istream": "cpp",
|
|
||||||
"limits": "cpp",
|
|
||||||
"list": "cpp",
|
|
||||||
"locale": "cpp",
|
|
||||||
"map": "cpp",
|
|
||||||
"mutex": "cpp",
|
|
||||||
"new": "cpp",
|
|
||||||
"optional": "cpp",
|
|
||||||
"ostream": "cpp",
|
|
||||||
"print": "cpp",
|
|
||||||
"queue": "cpp",
|
|
||||||
"ratio": "cpp",
|
|
||||||
"regex": "cpp",
|
|
||||||
"scoped_allocator": "cpp",
|
|
||||||
"set": "cpp",
|
|
||||||
"span": "cpp",
|
|
||||||
"sstream": "cpp",
|
|
||||||
"stack": "cpp",
|
|
||||||
"stdexcept": "cpp",
|
|
||||||
"streambuf": "cpp",
|
|
||||||
"string": "cpp",
|
|
||||||
"string_view": "cpp",
|
|
||||||
"tuple": "cpp",
|
|
||||||
"typeindex": "cpp",
|
|
||||||
"typeinfo": "cpp",
|
|
||||||
"unordered_map": "cpp",
|
|
||||||
"unordered_set": "cpp",
|
|
||||||
"valarray": "cpp",
|
|
||||||
"variant": "cpp",
|
|
||||||
"vector": "cpp",
|
|
||||||
"algorithm": "cpp"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
.vscode/tasks.json
vendored
13
.vscode/tasks.json
vendored
@ -1,19 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
|
||||||
"type": "sweetpad",
|
|
||||||
"action": "launch",
|
|
||||||
"problemMatcher": [
|
|
||||||
"$sweetpad-watch", // ! Required for debugging
|
|
||||||
"$sweetpad-xcodebuild-default",
|
|
||||||
"$sweetpad-xcbeautify-errors",
|
|
||||||
"$sweetpad-xcbeautify-warnings"
|
|
||||||
],
|
|
||||||
"label": "sweetpad: launch",
|
|
||||||
"detail": "Build and Launch the app",
|
|
||||||
"isBackground": true // ! Required for debugging
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -995,6 +995,11 @@ public enum SendInviteLinkScreenSubject {
|
|||||||
case groupCall(link: String)
|
case groupCall(link: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum StarsWithdrawalScreenSubject {
|
||||||
|
case withdraw
|
||||||
|
case enterAmount(current: StarsAmount)
|
||||||
|
}
|
||||||
|
|
||||||
public protocol SharedAccountContext: AnyObject {
|
public protocol SharedAccountContext: AnyObject {
|
||||||
var sharedContainerPath: String { get }
|
var sharedContainerPath: String { get }
|
||||||
var basePath: String { get }
|
var basePath: String { get }
|
||||||
@ -1179,7 +1184,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController
|
func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController
|
||||||
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
|
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
|
||||||
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
||||||
|
@ -4294,7 +4294,7 @@ enum GCDAsyncSocketConfig
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL hasBytesAvailable;
|
BOOL hasBytesAvailable = false;
|
||||||
unsigned long estimatedBytesAvailable;
|
unsigned long estimatedBytesAvailable;
|
||||||
|
|
||||||
if ([self usingCFStreamForTLS])
|
if ([self usingCFStreamForTLS])
|
||||||
|
@ -38,9 +38,10 @@ private final class ChannelPermissionsControllerArguments {
|
|||||||
let updateSlowmode: (Int32) -> Void
|
let updateSlowmode: (Int32) -> Void
|
||||||
let updateUnrestrictBoosters: (Int32) -> Void
|
let updateUnrestrictBoosters: (Int32) -> Void
|
||||||
let updateStarsAmount: (StarsAmount?, Bool) -> Void
|
let updateStarsAmount: (StarsAmount?, Bool) -> Void
|
||||||
|
let openSetCustomStarsAmount: () -> Void
|
||||||
let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void
|
let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, updateUnrestrictBoosters: @escaping (Int32) -> Void, updateStarsAmount: @escaping (StarsAmount?, Bool) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) {
|
init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, updateUnrestrictBoosters: @escaping (Int32) -> Void, updateStarsAmount: @escaping (StarsAmount?, Bool) -> Void, openSetCustomStarsAmount: @escaping () -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatePermission = updatePermission
|
self.updatePermission = updatePermission
|
||||||
self.addPeer = addPeer
|
self.addPeer = addPeer
|
||||||
@ -55,6 +56,7 @@ private final class ChannelPermissionsControllerArguments {
|
|||||||
self.updateSlowmode = updateSlowmode
|
self.updateSlowmode = updateSlowmode
|
||||||
self.updateUnrestrictBoosters = updateUnrestrictBoosters
|
self.updateUnrestrictBoosters = updateUnrestrictBoosters
|
||||||
self.updateStarsAmount = updateStarsAmount
|
self.updateStarsAmount = updateStarsAmount
|
||||||
|
self.openSetCustomStarsAmount = openSetCustomStarsAmount
|
||||||
self.toggleIsOptionExpanded = toggleIsOptionExpanded
|
self.toggleIsOptionExpanded = toggleIsOptionExpanded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -427,7 +429,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
|||||||
case let .messagePrice(_, value, maxValue, price):
|
case let .messagePrice(_, value, maxValue, price):
|
||||||
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, apply in
|
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, apply in
|
||||||
arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0), apply)
|
arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0), apply)
|
||||||
})
|
}, openSetCustom: nil)
|
||||||
case let .messagePriceInfo(_, value):
|
case let .messagePriceInfo(_, value):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section)
|
||||||
case let .unrestrictBoostersSwitch(_, title, value):
|
case let .unrestrictBoostersSwitch(_, title, value):
|
||||||
@ -1267,6 +1269,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
|
|||||||
|> deliverOnMainQueue).start())
|
|> deliverOnMainQueue).start())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}, openSetCustomStarsAmount: {
|
||||||
}, toggleIsOptionExpanded: { flags in
|
}, toggleIsOptionExpanded: { flags in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
|
@ -20,6 +20,7 @@ private final class IncomingMessagePrivacyScreenArguments {
|
|||||||
let infoLinkAction: () -> Void
|
let infoLinkAction: () -> Void
|
||||||
let openExceptions: () -> Void
|
let openExceptions: () -> Void
|
||||||
let openPremiumInfo: () -> Void
|
let openPremiumInfo: () -> Void
|
||||||
|
let openSetCustomStarsAmount: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -27,7 +28,8 @@ private final class IncomingMessagePrivacyScreenArguments {
|
|||||||
disabledValuePressed: @escaping () -> Void,
|
disabledValuePressed: @escaping () -> Void,
|
||||||
infoLinkAction: @escaping () -> Void,
|
infoLinkAction: @escaping () -> Void,
|
||||||
openExceptions: @escaping () -> Void,
|
openExceptions: @escaping () -> Void,
|
||||||
openPremiumInfo: @escaping () -> Void
|
openPremiumInfo: @escaping () -> Void,
|
||||||
|
openSetCustomStarsAmount: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updateValue = updateValue
|
self.updateValue = updateValue
|
||||||
@ -35,6 +37,7 @@ private final class IncomingMessagePrivacyScreenArguments {
|
|||||||
self.infoLinkAction = infoLinkAction
|
self.infoLinkAction = infoLinkAction
|
||||||
self.openExceptions = openExceptions
|
self.openExceptions = openExceptions
|
||||||
self.openPremiumInfo = openPremiumInfo
|
self.openPremiumInfo = openPremiumInfo
|
||||||
|
self.openSetCustomStarsAmount = openSetCustomStarsAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +154,8 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
|||||||
case let .price(value, maxValue, price, isEnabled):
|
case let .price(value, maxValue, price, isEnabled):
|
||||||
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, _ in
|
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, _ in
|
||||||
arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0)))
|
arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0)))
|
||||||
|
}, openSetCustom: {
|
||||||
|
arguments.openSetCustomStarsAmount()
|
||||||
}, openPremiumInfo: {
|
}, openPremiumInfo: {
|
||||||
arguments.openPremiumInfo()
|
arguments.openPremiumInfo()
|
||||||
})
|
})
|
||||||
@ -365,6 +370,20 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
|
|||||||
controller?.replace(with: c)
|
controller?.replace(with: c)
|
||||||
}
|
}
|
||||||
pushControllerImpl?(controller)
|
pushControllerImpl?(controller)
|
||||||
|
},
|
||||||
|
openSetCustomStarsAmount: {
|
||||||
|
var currentAmount: StarsAmount = StarsAmount(value: 1, nanos: 0)
|
||||||
|
if case let .paidMessages(value) = stateValue.with({ $0 }).updatedValue {
|
||||||
|
currentAmount = value
|
||||||
|
}
|
||||||
|
let starsScreen = context.sharedContext.makeStarsWithdrawalScreen(context: context, subject: .enterAmount(current: currentAmount), completion: { amount in
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0))
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pushControllerImpl?(starsScreen)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -121,6 +121,7 @@ swift_library(
|
|||||||
"//submodules/FastBlur",
|
"//submodules/FastBlur",
|
||||||
"//submodules/InviteLinksUI",
|
"//submodules/InviteLinksUI",
|
||||||
"//third-party/td:TdBinding",
|
"//third-party/td:TdBinding",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -335,6 +335,15 @@ private final class ConferenceCallE2EContextStateImpl: ConferenceCallE2EContextS
|
|||||||
func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant] {
|
func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant] {
|
||||||
return self.call.participants().map { ConferenceCallE2EContext.BlockchainParticipant(userId: $0.userId, internalId: $0.internalId) }
|
return self.call.participants().map { ConferenceCallE2EContext.BlockchainParticipant(userId: $0.userId, internalId: $0.internalId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getParticipantLatencies() -> [Int64: Double] {
|
||||||
|
let dict = self.call.participantLatencies()
|
||||||
|
var result: [Int64: Double] = [:]
|
||||||
|
for (k, v) in dict {
|
||||||
|
result[k.int64Value] = v.doubleValue
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func getParticipantIds() -> [Int64] {
|
func getParticipantIds() -> [Int64] {
|
||||||
return self.call.participants().map { $0.userId }
|
return self.call.participants().map { $0.userId }
|
||||||
|
@ -26,6 +26,7 @@ import TooltipUI
|
|||||||
import BlurredBackgroundComponent
|
import BlurredBackgroundComponent
|
||||||
import CallsEmoji
|
import CallsEmoji
|
||||||
import InviteLinksUI
|
import InviteLinksUI
|
||||||
|
import AnimatedTextComponent
|
||||||
|
|
||||||
extension VideoChatCall {
|
extension VideoChatCall {
|
||||||
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
||||||
@ -262,6 +263,9 @@ final class VideoChatScreenComponent: Component {
|
|||||||
var invitedPeers: [InvitedPeer] = []
|
var invitedPeers: [InvitedPeer] = []
|
||||||
var invitedPeersDisposable: Disposable?
|
var invitedPeersDisposable: Disposable?
|
||||||
|
|
||||||
|
var lastTitleEvent: String?
|
||||||
|
var lastTitleEventTimer: Foundation.Timer?
|
||||||
|
|
||||||
var speakingParticipantPeers: [EnginePeer] = []
|
var speakingParticipantPeers: [EnginePeer] = []
|
||||||
var visibleParticipants: Set<EnginePeer.Id> = Set()
|
var visibleParticipants: Set<EnginePeer.Id> = Set()
|
||||||
|
|
||||||
@ -320,6 +324,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.inviteDisposable.dispose()
|
self.inviteDisposable.dispose()
|
||||||
self.conferenceCallStateDisposable?.dispose()
|
self.conferenceCallStateDisposable?.dispose()
|
||||||
self.encryptionKeyEmojiDisposable?.dispose()
|
self.encryptionKeyEmojiDisposable?.dispose()
|
||||||
|
self.lastTitleEventTimer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
@ -1600,12 +1605,13 @@ final class VideoChatScreenComponent: Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.memberEventsDisposable?.dispose()
|
self.memberEventsDisposable?.dispose()
|
||||||
if groupCall.peerId != nil {
|
self.memberEventsDisposable = (groupCall.memberEvents
|
||||||
self.memberEventsDisposable = (groupCall.memberEvents
|
|> deliverOnMainQueue).start(next: { [weak self] event in
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] event in
|
guard let self, let members = self.members, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
||||||
guard let self, let members = self.members, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
if groupCall.peerId != nil {
|
||||||
if event.joined {
|
if event.joined {
|
||||||
var displayEvent = false
|
var displayEvent = false
|
||||||
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
||||||
@ -1624,8 +1630,30 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
}
|
if event.joined {
|
||||||
|
self.lastTitleEvent = "\(event.peer.compactDisplayTitle) joined"
|
||||||
|
} else {
|
||||||
|
self.lastTitleEvent = "\(event.peer.compactDisplayTitle) left"
|
||||||
|
}
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lastTitleEventTimer?.invalidate()
|
||||||
|
self.lastTitleEventTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.5, repeats: false, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.lastTitleEventTimer = nil
|
||||||
|
self.lastTitleEvent = nil
|
||||||
|
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
case let .conferenceSource(conferenceSource):
|
case let .conferenceSource(conferenceSource):
|
||||||
self.membersDisposable?.dispose()
|
self.membersDisposable?.dispose()
|
||||||
self.membersDisposable = (View.groupCallMembersForConferenceSource(conferenceSource: conferenceSource)
|
self.membersDisposable = (View.groupCallMembersForConferenceSource(conferenceSource: conferenceSource)
|
||||||
@ -1887,14 +1915,17 @@ final class VideoChatScreenComponent: Component {
|
|||||||
|
|
||||||
let maxSingleColumnWidth: CGFloat = 620.0
|
let maxSingleColumnWidth: CGFloat = 620.0
|
||||||
let isTwoColumnLayout: Bool
|
let isTwoColumnLayout: Bool
|
||||||
|
let isLandscape: Bool
|
||||||
if availableSize.width > maxSingleColumnWidth {
|
if availableSize.width > maxSingleColumnWidth {
|
||||||
if let mappedParticipants, mappedParticipants.participants.contains(where: { $0.videoDescription != nil || $0.presentationDescription != nil }) {
|
if let mappedParticipants, mappedParticipants.participants.contains(where: { $0.videoDescription != nil || $0.presentationDescription != nil }) {
|
||||||
isTwoColumnLayout = true
|
isTwoColumnLayout = true
|
||||||
} else {
|
} else {
|
||||||
isTwoColumnLayout = false
|
isTwoColumnLayout = false
|
||||||
}
|
}
|
||||||
|
isLandscape = true
|
||||||
} else {
|
} else {
|
||||||
isTwoColumnLayout = false
|
isTwoColumnLayout = false
|
||||||
|
isLandscape = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var containerOffset: CGFloat = 0.0
|
var containerOffset: CGFloat = 0.0
|
||||||
@ -1935,24 +1966,32 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let landscapeControlsWidth: CGFloat = 88.0
|
let landscapeControlsWidth: CGFloat = 104.0
|
||||||
let landscapeControlsOffsetX: CGFloat = 18.0
|
var landscapeControlsOffsetX: CGFloat = 0.0
|
||||||
let landscapeControlsSpacing: CGFloat = 30.0
|
let landscapeControlsSpacing: CGFloat = 30.0
|
||||||
|
|
||||||
let leftInset: CGFloat = max(environment.safeInsets.left, 14.0)
|
var leftInset: CGFloat = max(environment.safeInsets.left, 14.0)
|
||||||
|
|
||||||
var rightInset: CGFloat = max(environment.safeInsets.right, 14.0)
|
var rightInset: CGFloat = max(environment.safeInsets.right, 14.0)
|
||||||
var buttonsOnTheSide = false
|
var buttonsOnTheSide = false
|
||||||
if availableSize.width > maxSingleColumnWidth && !environment.metrics.isTablet {
|
if availableSize.width > maxSingleColumnWidth && !environment.metrics.isTablet {
|
||||||
|
leftInset += 2.0
|
||||||
|
rightInset += 2.0
|
||||||
|
|
||||||
buttonsOnTheSide = true
|
buttonsOnTheSide = true
|
||||||
rightInset += landscapeControlsWidth
|
if case .landscapeLeft = environment.orientation {
|
||||||
|
rightInset = max(rightInset, environment.safeInsets.left + landscapeControlsWidth)
|
||||||
|
landscapeControlsOffsetX = -environment.safeInsets.left
|
||||||
|
} else {
|
||||||
|
rightInset = max(rightInset, landscapeControlsWidth)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let topInset: CGFloat = environment.statusBarHeight + 2.0
|
let topInset: CGFloat = environment.statusBarHeight + 2.0
|
||||||
let navigationBarHeight: CGFloat = 61.0
|
let navigationBarHeight: CGFloat = 61.0
|
||||||
var navigationHeight = topInset + navigationBarHeight
|
var navigationHeight = topInset + navigationBarHeight
|
||||||
|
|
||||||
let navigationButtonAreaWidth: CGFloat = 40.0
|
let navigationButtonAreaWidth: CGFloat = 34.0
|
||||||
let navigationButtonDiameter: CGFloat = 28.0
|
let navigationButtonDiameter: CGFloat = 28.0
|
||||||
|
|
||||||
let navigationLeftButtonSize = self.navigationLeftButton.update(
|
let navigationLeftButtonSize = self.navigationLeftButton.update(
|
||||||
@ -2013,7 +2052,10 @@ final class VideoChatScreenComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
|
var navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
|
||||||
|
if buttonsOnTheSide {
|
||||||
|
navigationRightButtonFrame.origin.x += 42.0
|
||||||
|
}
|
||||||
if let navigationRightButtonView = self.navigationRightButton.view {
|
if let navigationRightButtonView = self.navigationRightButton.view {
|
||||||
if navigationRightButtonView.superview == nil {
|
if navigationRightButtonView.superview == nil {
|
||||||
self.containerView.addSubview(navigationRightButtonView)
|
self.containerView.addSubview(navigationRightButtonView)
|
||||||
@ -2022,6 +2064,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: navigationRightButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
alphaTransition.setAlpha(view: navigationRightButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var navigationSidebarButtonFrame: CGRect?
|
||||||
if isTwoColumnLayout {
|
if isTwoColumnLayout {
|
||||||
var navigationSidebarButtonTransition = transition
|
var navigationSidebarButtonTransition = transition
|
||||||
let navigationSidebarButton: ComponentView<Empty>
|
let navigationSidebarButton: ComponentView<Empty>
|
||||||
@ -2057,7 +2100,8 @@ final class VideoChatScreenComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter)
|
containerSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter)
|
||||||
)
|
)
|
||||||
let navigationSidebarButtonFrame = CGRect(origin: CGPoint(x: navigationRightButtonFrame.minX - 32.0 - navigationSidebarButtonSize.width, y: topInset + floor((navigationBarHeight - navigationSidebarButtonSize.height) * 0.5)), size: navigationSidebarButtonSize)
|
let navigationSidebarButtonFrameValue = CGRect(origin: CGPoint(x: navigationRightButtonFrame.minX - 21.0 - navigationSidebarButtonSize.width, y: topInset + floor((navigationBarHeight - navigationSidebarButtonSize.height) * 0.5)), size: navigationSidebarButtonSize)
|
||||||
|
navigationSidebarButtonFrame = navigationSidebarButtonFrameValue
|
||||||
if let navigationSidebarButtonView = navigationSidebarButton.view {
|
if let navigationSidebarButtonView = navigationSidebarButton.view {
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
if navigationSidebarButtonView.superview == nil {
|
if navigationSidebarButtonView.superview == nil {
|
||||||
@ -2066,7 +2110,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.containerView.insertSubview(navigationSidebarButtonView, aboveSubview: navigationRightButtonView)
|
self.containerView.insertSubview(navigationSidebarButtonView, aboveSubview: navigationRightButtonView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
navigationSidebarButtonTransition.setFrame(view: navigationSidebarButtonView, frame: navigationSidebarButtonFrame)
|
navigationSidebarButtonTransition.setFrame(view: navigationSidebarButtonView, frame: navigationSidebarButtonFrameValue)
|
||||||
if animateIn {
|
if animateIn {
|
||||||
transition.animateScale(view: navigationSidebarButtonView, from: 0.001, to: 1.0)
|
transition.animateScale(view: navigationSidebarButtonView, from: 0.001, to: 1.0)
|
||||||
transition.animateAlpha(view: navigationSidebarButtonView, from: 0.0, to: 1.0)
|
transition.animateAlpha(view: navigationSidebarButtonView, from: 0.0, to: 1.0)
|
||||||
@ -2082,17 +2126,27 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let idleTitleStatusText: String
|
var idleTitleStatusText: [AnimatedTextComponent.Item] = []
|
||||||
if let callState = self.callState {
|
if let callState = self.callState {
|
||||||
if callState.networkState == .connected, let members = self.members {
|
if callState.networkState == .connected, let members = self.members {
|
||||||
idleTitleStatusText = environment.strings.VoiceChat_Panel_Members(Int32(max(1, members.totalCount)))
|
//TODO:localize
|
||||||
|
let totalCount = max(1, members.totalCount)
|
||||||
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .number(totalCount, minDigits: 0)))
|
||||||
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(1), isUnbreakable: false, content: .text(totalCount == 1 ? " participant" : " participants")))
|
||||||
|
if let lastTitleEvent = self.lastTitleEvent {
|
||||||
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(6), isUnbreakable: false, content: .text(", \(lastTitleEvent)")))
|
||||||
|
} else if !self.invitedPeers.isEmpty {
|
||||||
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(3), isUnbreakable: true, content: .text(", ")))
|
||||||
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(4), isUnbreakable: false, content: .number(self.invitedPeers.count, minDigits: 0)))
|
||||||
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(5), isUnbreakable: false, content: .text(" invited")))
|
||||||
|
}
|
||||||
} else if callState.scheduleTimestamp != nil {
|
} else if callState.scheduleTimestamp != nil {
|
||||||
idleTitleStatusText = environment.strings.VoiceChat_Scheduled
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(environment.strings.VoiceChat_Scheduled)))
|
||||||
} else {
|
} else {
|
||||||
idleTitleStatusText = environment.strings.VoiceChat_Connecting
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(environment.strings.VoiceChat_Connecting)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
idleTitleStatusText = " "
|
idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(" ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
let canManageCall = self.callState?.canManageCall ?? false
|
let canManageCall = self.callState?.canManageCall ?? false
|
||||||
@ -2108,6 +2162,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? environment.strings.VideoChat_GroupCallTitle,
|
title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? environment.strings.VideoChat_GroupCallTitle,
|
||||||
status: idleTitleStatusText,
|
status: idleTitleStatusText,
|
||||||
isRecording: self.callState?.recordingStartTimestamp != nil,
|
isRecording: self.callState?.recordingStartTimestamp != nil,
|
||||||
|
isLandscape: isLandscape,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
tapAction: self.callState?.recordingStartTimestamp != nil ? { [weak self] in
|
tapAction: self.callState?.recordingStartTimestamp != nil ? { [weak self] in
|
||||||
guard let self, let environment = self.environment, let currentCall = self.currentCall else {
|
guard let self, let environment = self.environment, let currentCall = self.currentCall else {
|
||||||
@ -2146,7 +2201,12 @@ final class VideoChatScreenComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: maxTitleWidth, height: 100.0)
|
containerSize: CGSize(width: maxTitleWidth, height: 100.0)
|
||||||
)
|
)
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + floor((availableSize.width - leftInset - rightInset - titleSize.width) * 0.5), y: topInset + floor((navigationBarHeight - titleSize.height) * 0.5)), size: titleSize)
|
var titleFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + floor((navigationBarHeight - titleSize.height) * 0.5)), size: titleSize)
|
||||||
|
if isLandscape {
|
||||||
|
titleFrame.origin.x = navigationLeftButtonFrame.maxX + 20.0
|
||||||
|
} else {
|
||||||
|
titleFrame.origin.x = leftInset + floor((availableSize.width - leftInset - rightInset - titleSize.width) * 0.5)
|
||||||
|
}
|
||||||
if let titleView = self.title.view {
|
if let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
self.containerView.addSubview(titleView)
|
self.containerView.addSubview(titleView)
|
||||||
@ -2155,6 +2215,27 @@ final class VideoChatScreenComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: titleView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
alphaTransition.setAlpha(view: titleView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let areButtonsCollapsed: Bool
|
||||||
|
let mainColumnWidth: CGFloat
|
||||||
|
let mainColumnSideInset: CGFloat
|
||||||
|
|
||||||
|
if isTwoColumnLayout {
|
||||||
|
areButtonsCollapsed = false
|
||||||
|
|
||||||
|
mainColumnWidth = min(isLandscape ? 340.0 : 320.0, availableSize.width - leftInset - rightInset - 340.0)
|
||||||
|
mainColumnSideInset = 0.0
|
||||||
|
} else {
|
||||||
|
areButtonsCollapsed = self.expandedParticipantsVideoState != nil
|
||||||
|
|
||||||
|
if availableSize.width > maxSingleColumnWidth {
|
||||||
|
mainColumnWidth = 420.0
|
||||||
|
mainColumnSideInset = 0.0
|
||||||
|
} else {
|
||||||
|
mainColumnWidth = availableSize.width
|
||||||
|
mainColumnSideInset = max(leftInset, rightInset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var encryptionKeyFrame: CGRect?
|
var encryptionKeyFrame: CGRect?
|
||||||
var isConference = false
|
var isConference = false
|
||||||
if case let .group(groupCall) = self.currentCall {
|
if case let .group(groupCall) = self.currentCall {
|
||||||
@ -2163,7 +2244,9 @@ final class VideoChatScreenComponent: Component {
|
|||||||
isConference = true
|
isConference = true
|
||||||
}
|
}
|
||||||
if isConference {
|
if isConference {
|
||||||
navigationHeight -= 2.0
|
if !isLandscape {
|
||||||
|
navigationHeight -= 2.0
|
||||||
|
}
|
||||||
let encryptionKey: ComponentView<Empty>
|
let encryptionKey: ComponentView<Empty>
|
||||||
var encryptionKeyTransition = transition
|
var encryptionKeyTransition = transition
|
||||||
if let current = self.encryptionKey {
|
if let current = self.encryptionKey {
|
||||||
@ -2194,11 +2277,34 @@ final class VideoChatScreenComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: min(400.0, availableSize.width - leftInset - rightInset - 16.0 * 2.0), height: 10000.0)
|
containerSize: CGSize(width: min(400.0, availableSize.width - leftInset - rightInset - 16.0 * 2.0), height: 10000.0)
|
||||||
)
|
)
|
||||||
let encryptionKeyFrameValue = CGRect(origin: CGPoint(x: leftInset + floor((availableSize.width - leftInset - rightInset - encryptionKeySize.width) * 0.5), y: navigationHeight), size: encryptionKeySize)
|
var encryptionKeyFrameValue = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: encryptionKeySize)
|
||||||
|
if isLandscape {
|
||||||
|
let maxEncryptionKeyX: CGFloat
|
||||||
|
if let navigationSidebarButtonFrame {
|
||||||
|
maxEncryptionKeyX = navigationSidebarButtonFrame.minX - 8.0 - encryptionKeySize.width
|
||||||
|
} else {
|
||||||
|
maxEncryptionKeyX = navigationRightButtonFrame.minX - 8.0 - encryptionKeySize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
let idealEncryptionKeyX: CGFloat
|
||||||
|
if isTwoColumnLayout {
|
||||||
|
idealEncryptionKeyX = availableSize.width - rightInset - mainColumnWidth
|
||||||
|
} else {
|
||||||
|
idealEncryptionKeyX = maxEncryptionKeyX - 13.0
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptionKeyFrameValue.origin.x = min(idealEncryptionKeyX, maxEncryptionKeyX)
|
||||||
|
encryptionKeyFrameValue.origin.y = navigationLeftButtonFrame.minY + floorToScreenPixels((navigationLeftButtonFrame.height - encryptionKeySize.height) * 0.5)
|
||||||
|
} else {
|
||||||
|
encryptionKeyFrameValue.origin.x = leftInset + floor((availableSize.width - leftInset - rightInset - encryptionKeySize.width) * 0.5)
|
||||||
|
encryptionKeyFrameValue.origin.y = navigationHeight
|
||||||
|
}
|
||||||
encryptionKeyFrame = encryptionKeyFrameValue
|
encryptionKeyFrame = encryptionKeyFrameValue
|
||||||
|
|
||||||
navigationHeight += encryptionKeySize.height
|
if !isLandscape {
|
||||||
navigationHeight += 16.0
|
navigationHeight += encryptionKeySize.height
|
||||||
|
navigationHeight += 16.0
|
||||||
|
}
|
||||||
} else if let encryptionKey = self.encryptionKey {
|
} else if let encryptionKey = self.encryptionKey {
|
||||||
self.encryptionKey = nil
|
self.encryptionKey = nil
|
||||||
encryptionKey.view?.removeFromSuperview()
|
encryptionKey.view?.removeFromSuperview()
|
||||||
@ -2207,27 +2313,6 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.encryptionKeyBackground = nil
|
self.encryptionKeyBackground = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let areButtonsCollapsed: Bool
|
|
||||||
let mainColumnWidth: CGFloat
|
|
||||||
let mainColumnSideInset: CGFloat
|
|
||||||
|
|
||||||
if isTwoColumnLayout {
|
|
||||||
areButtonsCollapsed = false
|
|
||||||
|
|
||||||
mainColumnWidth = 320.0
|
|
||||||
mainColumnSideInset = 0.0
|
|
||||||
} else {
|
|
||||||
areButtonsCollapsed = self.expandedParticipantsVideoState != nil
|
|
||||||
|
|
||||||
if availableSize.width > maxSingleColumnWidth {
|
|
||||||
mainColumnWidth = 420.0
|
|
||||||
mainColumnSideInset = 0.0
|
|
||||||
} else {
|
|
||||||
mainColumnWidth = availableSize.width
|
|
||||||
mainColumnSideInset = max(leftInset, rightInset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let actionButtonDiameter: CGFloat = 56.0
|
let actionButtonDiameter: CGFloat = 56.0
|
||||||
let expandedMicrophoneButtonDiameter: CGFloat = actionButtonDiameter
|
let expandedMicrophoneButtonDiameter: CGFloat = actionButtonDiameter
|
||||||
var collapsedMicrophoneButtonDiameter: CGFloat = 116.0
|
var collapsedMicrophoneButtonDiameter: CGFloat = 116.0
|
||||||
@ -2285,7 +2370,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
|
|
||||||
if buttonsOnTheSide {
|
if buttonsOnTheSide {
|
||||||
collapsedMicrophoneButtonFrame.origin.y = floor((availableSize.height - actionButtonDiameter) * 0.5)
|
collapsedMicrophoneButtonFrame.origin.y = floor((availableSize.height - actionButtonDiameter) * 0.5)
|
||||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - environment.safeInsets.right - landscapeControlsWidth + landscapeControlsOffsetX
|
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - landscapeControlsWidth + landscapeControlsOffsetX + floor((landscapeControlsWidth - actionButtonDiameter) * 0.5)
|
||||||
|
|
||||||
if isMainColumnHidden {
|
if isMainColumnHidden {
|
||||||
collapsedMicrophoneButtonFrame.origin.x += mainColumnWidth + landscapeControlsWidth
|
collapsedMicrophoneButtonFrame.origin.x += mainColumnWidth + landscapeControlsWidth
|
||||||
|
@ -6,19 +6,22 @@ import MultilineTextComponent
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import HierarchyTrackingLayer
|
import HierarchyTrackingLayer
|
||||||
import ChatTitleActivityNode
|
import ChatTitleActivityNode
|
||||||
|
import AnimatedTextComponent
|
||||||
|
|
||||||
final class VideoChatTitleComponent: Component {
|
final class VideoChatTitleComponent: Component {
|
||||||
let title: String
|
let title: String
|
||||||
let status: String
|
let status: [AnimatedTextComponent.Item]
|
||||||
let isRecording: Bool
|
let isRecording: Bool
|
||||||
|
let isLandscape: Bool
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let tapAction: (() -> Void)?
|
let tapAction: (() -> Void)?
|
||||||
let longTapAction: (() -> Void)?
|
let longTapAction: (() -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
title: String,
|
title: String,
|
||||||
status: String,
|
status: [AnimatedTextComponent.Item],
|
||||||
isRecording: Bool,
|
isRecording: Bool,
|
||||||
|
isLandscape: Bool,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
tapAction: (() -> Void)?,
|
tapAction: (() -> Void)?,
|
||||||
longTapAction: (() -> Void)?
|
longTapAction: (() -> Void)?
|
||||||
@ -26,6 +29,7 @@ final class VideoChatTitleComponent: Component {
|
|||||||
self.title = title
|
self.title = title
|
||||||
self.status = status
|
self.status = status
|
||||||
self.isRecording = isRecording
|
self.isRecording = isRecording
|
||||||
|
self.isLandscape = isLandscape
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.tapAction = tapAction
|
self.tapAction = tapAction
|
||||||
self.longTapAction = longTapAction
|
self.longTapAction = longTapAction
|
||||||
@ -41,6 +45,9 @@ final class VideoChatTitleComponent: Component {
|
|||||||
if lhs.isRecording != rhs.isRecording {
|
if lhs.isRecording != rhs.isRecording {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isLandscape != rhs.isLandscape {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -211,12 +218,14 @@ final class VideoChatTitleComponent: Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let statusComponent: AnyComponent<Empty>
|
let statusComponent: AnyComponent<Empty>
|
||||||
statusComponent = AnyComponent(MultilineTextComponent(
|
statusComponent = AnyComponent(AnimatedTextComponent(
|
||||||
text: .plain(NSAttributedString(string: component.status, font: Font.regular(13.0), textColor: UIColor(white: 1.0, alpha: 0.5)))
|
font: Font.regular(13.0),
|
||||||
|
color: UIColor(white: 1.0, alpha: 0.5),
|
||||||
|
items: component.status
|
||||||
))
|
))
|
||||||
|
|
||||||
let statusSize = self.status.update(
|
let statusSize = self.status.update(
|
||||||
transition: .immediate,
|
transition: transition,
|
||||||
component: statusComponent,
|
component: statusComponent,
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
@ -224,7 +233,10 @@ final class VideoChatTitleComponent: Component {
|
|||||||
|
|
||||||
let size = CGSize(width: availableSize.width, height: titleSize.height + spacing + statusSize.height)
|
let size = CGSize(width: availableSize.width, height: titleSize.height + spacing + statusSize.height)
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: 0.0), size: titleSize)
|
var titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize)
|
||||||
|
if !component.isLandscape {
|
||||||
|
titleFrame.origin.x = floor((size.width - titleSize.width) * 0.5)
|
||||||
|
}
|
||||||
if let titleView = self.title.view {
|
if let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
titleView.layer.anchorPoint = CGPoint()
|
titleView.layer.anchorPoint = CGPoint()
|
||||||
@ -235,13 +247,17 @@ final class VideoChatTitleComponent: Component {
|
|||||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusFrame = CGRect(origin: CGPoint(x: floor((size.width - statusSize.width) * 0.5), y: titleFrame.maxY + spacing), size: statusSize)
|
var statusFrame = CGRect(origin: CGPoint(x: 0.0, y: titleFrame.maxY + spacing), size: statusSize)
|
||||||
|
if !component.isLandscape {
|
||||||
|
statusFrame.origin.x = floor((size.width - statusSize.width) * 0.5)
|
||||||
|
}
|
||||||
if let statusView = self.status.view {
|
if let statusView = self.status.view {
|
||||||
if statusView.superview == nil {
|
if statusView.superview == nil {
|
||||||
|
statusView.layer.anchorPoint = CGPoint()
|
||||||
statusView.isUserInteractionEnabled = false
|
statusView.isUserInteractionEnabled = false
|
||||||
self.addSubview(statusView)
|
self.addSubview(statusView)
|
||||||
}
|
}
|
||||||
transition.setPosition(view: statusView, position: statusFrame.center)
|
transition.setPosition(view: statusView, position: statusFrame.origin)
|
||||||
statusView.bounds = CGRect(origin: CGPoint(), size: statusFrame.size)
|
statusView.bounds = CGRect(origin: CGPoint(), size: statusFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,9 +53,6 @@ func closeButtonImage(dark: Bool) -> UIImage? {
|
|||||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.setLineWidth(2.0)
|
context.setLineWidth(2.0)
|
||||||
context.setLineCap(.round)
|
context.setLineCap(.round)
|
||||||
context.setStrokeColor(UIColor.white.cgColor)
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
@ -5,6 +5,7 @@ public protocol ConferenceCallE2EContextState: AnyObject {
|
|||||||
func getEmojiState() -> Data?
|
func getEmojiState() -> Data?
|
||||||
func getParticipantIds() -> [Int64]
|
func getParticipantIds() -> [Int64]
|
||||||
func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant]
|
func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant]
|
||||||
|
func getParticipantLatencies() -> [Int64: Double]
|
||||||
|
|
||||||
func applyBlock(block: Data)
|
func applyBlock(block: Data)
|
||||||
func applyBroadcastBlock(block: Data)
|
func applyBroadcastBlock(block: Data)
|
||||||
@ -166,7 +167,7 @@ public final class ConferenceCallE2EContext {
|
|||||||
let keyPair = self.keyPair
|
let keyPair = self.keyPair
|
||||||
let userId = self.userId
|
let userId = self.userId
|
||||||
let initializeState = self.initializeState
|
let initializeState = self.initializeState
|
||||||
let (outBlocks, outEmoji, outBlockchainParticipants) = self.state.with({ callState -> ([Data], Data, [BlockchainParticipant]) in
|
let (outBlocks, outEmoji, outBlockchainParticipants, participantLatencies) = self.state.with({ callState -> ([Data], Data, [BlockchainParticipant], [Int64: Double]) in
|
||||||
if let state = callState.state {
|
if let state = callState.state {
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
if subChainId == 0 {
|
if subChainId == 0 {
|
||||||
@ -175,26 +176,26 @@ public final class ConferenceCallE2EContext {
|
|||||||
state.applyBroadcastBlock(block: block)
|
state.applyBroadcastBlock(block: block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants())
|
return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants(), state.getParticipantLatencies())
|
||||||
} else {
|
} else {
|
||||||
if subChainId == 0 {
|
if subChainId == 0 {
|
||||||
guard let block = blocks.last else {
|
guard let block = blocks.last else {
|
||||||
return ([], Data(), [])
|
return ([], Data(), [], [:])
|
||||||
}
|
}
|
||||||
guard let state = initializeState(keyPair, userId, block) else {
|
guard let state = initializeState(keyPair, userId, block) else {
|
||||||
return ([], Data(), [])
|
return ([], Data(), [], [:])
|
||||||
}
|
}
|
||||||
callState.state = state
|
callState.state = state
|
||||||
for block in callState.pendingIncomingBroadcastBlocks {
|
for block in callState.pendingIncomingBroadcastBlocks {
|
||||||
state.applyBroadcastBlock(block: block)
|
state.applyBroadcastBlock(block: block)
|
||||||
}
|
}
|
||||||
callState.pendingIncomingBroadcastBlocks.removeAll()
|
callState.pendingIncomingBroadcastBlocks.removeAll()
|
||||||
return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants())
|
return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants(), state.getParticipantLatencies())
|
||||||
} else if subChainId == 1 {
|
} else if subChainId == 1 {
|
||||||
callState.pendingIncomingBroadcastBlocks.append(contentsOf: blocks)
|
callState.pendingIncomingBroadcastBlocks.append(contentsOf: blocks)
|
||||||
return ([], Data(), [])
|
return ([], Data(), [], [:])
|
||||||
} else {
|
} else {
|
||||||
return ([], Data(), [])
|
return ([], Data(), [], [:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -204,6 +205,10 @@ public final class ConferenceCallE2EContext {
|
|||||||
for outBlock in outBlocks {
|
for outBlock in outBlocks {
|
||||||
let _ = self.engine.calls.sendConferenceCallBroadcast(callId: self.callId, accessHash: self.accessHash, block: outBlock).startStandalone()
|
let _ = self.engine.calls.sendConferenceCallBroadcast(callId: self.callId, accessHash: self.accessHash, block: outBlock).startStandalone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
print("Latencies: \(participantLatencies)")
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func e2ePoll(subChainId: Int) {
|
private func e2ePoll(subChainId: Int) {
|
||||||
|
@ -26,9 +26,10 @@ public final class MessagePriceItem: ListViewItem, ItemListItem {
|
|||||||
let price: String
|
let price: String
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let updated: (Int64, Bool) -> Void
|
let updated: (Int64, Bool) -> Void
|
||||||
|
let openSetCustom: (() -> Void)?
|
||||||
let openPremiumInfo: (() -> Void)?
|
let openPremiumInfo: (() -> Void)?
|
||||||
|
|
||||||
public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64, Bool) -> Void, openPremiumInfo: (() -> Void)? = nil) {
|
public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64, Bool) -> Void, openSetCustom: (() -> Void)? = nil, openPremiumInfo: (() -> Void)? = nil) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
@ -38,6 +39,7 @@ public final class MessagePriceItem: ListViewItem, ItemListItem {
|
|||||||
self.price = price
|
self.price = price
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.updated = updated
|
self.updated = updated
|
||||||
|
self.openSetCustom = openSetCustom
|
||||||
self.openPremiumInfo = openPremiumInfo
|
self.openPremiumInfo = openPremiumInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +163,8 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
private var sliderView: TGPhotoEditorSliderView?
|
private var sliderView: TGPhotoEditorSliderView?
|
||||||
private let leftTextNode: ImmediateTextNode
|
private let leftTextNode: ImmediateTextNode
|
||||||
private let rightTextNode: ImmediateTextNode
|
private let rightTextNode: ImmediateTextNode
|
||||||
|
private let centerTextButtonNode: HighlightableButtonNode
|
||||||
|
private let centerTextButtonBackground: UIImageView
|
||||||
private let centerLeftTextNode: ImmediateTextNode
|
private let centerLeftTextNode: ImmediateTextNode
|
||||||
private let centerRightTextNode: ImmediateTextNode
|
private let centerRightTextNode: ImmediateTextNode
|
||||||
private let lockIconNode: ASImageNode
|
private let lockIconNode: ASImageNode
|
||||||
@ -186,8 +190,13 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
self.leftTextNode = ImmediateTextNode()
|
self.leftTextNode = ImmediateTextNode()
|
||||||
self.rightTextNode = ImmediateTextNode()
|
self.rightTextNode = ImmediateTextNode()
|
||||||
|
|
||||||
|
self.centerTextButtonNode = HighlightableButtonNode()
|
||||||
|
self.centerTextButtonBackground = UIImageView()
|
||||||
self.centerLeftTextNode = ImmediateTextNode()
|
self.centerLeftTextNode = ImmediateTextNode()
|
||||||
|
self.centerLeftTextNode.isUserInteractionEnabled = false
|
||||||
self.centerRightTextNode = ImmediateTextNode()
|
self.centerRightTextNode = ImmediateTextNode()
|
||||||
|
self.centerRightTextNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.lockIconNode = ASImageNode()
|
self.lockIconNode = ASImageNode()
|
||||||
self.lockIconNode.displaysAsynchronously = false
|
self.lockIconNode.displaysAsynchronously = false
|
||||||
@ -198,9 +207,13 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
self.addSubnode(self.leftTextNode)
|
self.addSubnode(self.leftTextNode)
|
||||||
self.addSubnode(self.rightTextNode)
|
self.addSubnode(self.rightTextNode)
|
||||||
self.addSubnode(self.centerLeftTextNode)
|
self.addSubnode(self.centerTextButtonNode)
|
||||||
self.addSubnode(self.centerRightTextNode)
|
self.centerTextButtonNode.view.addSubview(self.centerTextButtonBackground)
|
||||||
|
self.centerTextButtonNode.addSubnode(self.centerLeftTextNode)
|
||||||
|
self.centerTextButtonNode.addSubnode(self.centerRightTextNode)
|
||||||
self.addSubnode(self.lockIconNode)
|
self.addSubnode(self.lockIconNode)
|
||||||
|
|
||||||
|
self.centerTextButtonNode.addTarget(self, action: #selector(self.centerTextButtonPressed), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -231,7 +244,11 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
|
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
|
||||||
self.sliderView = sliderView
|
self.sliderView = sliderView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func centerTextButtonPressed() {
|
||||||
|
self.item?.openSetCustom?()
|
||||||
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: MessagePriceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: MessagePriceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let currentItem = self.item
|
let currentItem = self.item
|
||||||
|
|
||||||
@ -312,8 +329,8 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
|
strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
let centralLeftText = item.strings.Privacy_Messages_Stars(Int32(item.value))
|
let centralLeftText = item.strings.Privacy_Messages_Stars(Int32(item.value))
|
||||||
strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.theme.list.itemPrimaryTextColor)
|
strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor)
|
||||||
strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.theme.list.itemSecondaryTextColor)
|
strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor.withMultipliedAlpha(0.5) : item.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
let leftTextSize = strongSelf.leftTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
let leftTextSize = strongSelf.leftTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
@ -328,10 +345,28 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let totalCenterWidth = centerLeftTextSize.width + centerSpacing + centerRightTextSize.width
|
let totalCenterWidth = centerLeftTextSize.width + centerSpacing + centerRightTextSize.width
|
||||||
let centerLeftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalCenterWidth) / 2.0), y: 11.0), size: centerLeftTextSize)
|
let centerLeftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalCenterWidth) / 2.0), y: 11.0), size: centerLeftTextSize)
|
||||||
strongSelf.centerLeftTextNode.frame = centerLeftFrame
|
|
||||||
|
|
||||||
let centerRightFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalCenterWidth) / 2.0) + totalCenterWidth - centerRightTextSize.width, y: 14.0 - UIScreenPixel), size: centerRightTextSize)
|
let centerRightFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalCenterWidth) / 2.0) + totalCenterWidth - centerRightTextSize.width, y: 14.0 - UIScreenPixel), size: centerRightTextSize)
|
||||||
strongSelf.centerRightTextNode.frame = centerRightFrame
|
|
||||||
|
let centerButtonFrame = CGRect(origin: CGPoint(x: centerLeftFrame.minX, y: centerLeftFrame.minY), size: CGSize(width: centerRightFrame.maxX - centerLeftFrame.minX, height: centerLeftFrame.height)).insetBy(dx: -8.0, dy: -4.0)
|
||||||
|
|
||||||
|
strongSelf.centerTextButtonNode.frame = centerButtonFrame
|
||||||
|
|
||||||
|
strongSelf.centerTextButtonBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: centerButtonFrame.size)
|
||||||
|
if strongSelf.centerTextButtonBackground.image == nil {
|
||||||
|
strongSelf.centerTextButtonBackground.image = generateStretchableFilledCircleImage(diameter: 16.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
|
}
|
||||||
|
strongSelf.centerTextButtonBackground.tintColor = item.theme.list.itemAccentColor.withMultipliedAlpha(0.1)
|
||||||
|
|
||||||
|
if item.openSetCustom != nil {
|
||||||
|
strongSelf.centerTextButtonNode.isEnabled = true
|
||||||
|
strongSelf.centerTextButtonBackground.isHidden = false
|
||||||
|
} else {
|
||||||
|
strongSelf.centerTextButtonNode.isEnabled = false
|
||||||
|
strongSelf.centerTextButtonBackground.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.centerLeftTextNode.frame = centerLeftFrame.offsetBy(dx: -centerButtonFrame.minX, dy: -centerButtonFrame.minY)
|
||||||
|
strongSelf.centerRightTextNode.frame = centerRightFrame.offsetBy(dx: -centerButtonFrame.minX, dy: -centerButtonFrame.minY)
|
||||||
|
|
||||||
if let sliderView = strongSelf.sliderView {
|
if let sliderView = strongSelf.sliderView {
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
@ -343,12 +378,17 @@ private class MessagePriceItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0))
|
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0))
|
||||||
|
|
||||||
sliderView.interactionEnded = { [weak self] in
|
sliderView.interactionEnded = {
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.item?.updated(Int64(self.amount.realValue), true)
|
self.item?.updated(Int64(self.amount.realValue), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !sliderView.isTracking {
|
||||||
|
strongSelf.amount = Amount(realValue: Int(item.value), maxRealValue: Int(item.maxValue), maxSliderValue: 999, isLogarithmic: true)
|
||||||
|
sliderView.value = CGFloat(strongSelf.amount.sliderValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.lockIconNode.isHidden = item.isEnabled
|
strongSelf.lockIconNode.isHidden = item.isEnabled
|
||||||
|
@ -156,6 +156,15 @@ private final class SheetContent: CombinedComponent {
|
|||||||
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
|
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
|
||||||
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
|
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
|
||||||
amountLabel = nil
|
amountLabel = nil
|
||||||
|
case .paidMessages:
|
||||||
|
//TODO:localize
|
||||||
|
titleString = "Price per Message"
|
||||||
|
amountTitle = "PRICE IN STARS"
|
||||||
|
amountPlaceholder = "Enter Price"
|
||||||
|
|
||||||
|
minAmount = StarsAmount(value: 1, nanos: 0)
|
||||||
|
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
|
||||||
|
amountLabel = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
@ -280,6 +289,19 @@ private final class SheetContent: CombinedComponent {
|
|||||||
text: .plain(amountInfoString),
|
text: .plain(amountInfoString),
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
))
|
))
|
||||||
|
case .paidMessages:
|
||||||
|
let amountInfoString: NSAttributedString
|
||||||
|
if let value = state.amount?.value, value > 0 {
|
||||||
|
let fullValue: Int64 = Int64(value) * 1_000_000_000 * 80 / 100
|
||||||
|
let amountValue = StarsAmount(value: fullValue / 1_000_000_000, nanos: Int32(fullValue % 1_000_000_000))
|
||||||
|
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(amountValue) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||||
|
} else {
|
||||||
|
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **80%**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||||
|
}
|
||||||
|
amountFooter = AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(amountInfoString),
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
))
|
||||||
default:
|
default:
|
||||||
amountFooter = nil
|
amountFooter = nil
|
||||||
}
|
}
|
||||||
@ -340,6 +362,9 @@ private final class SheetContent: CombinedComponent {
|
|||||||
} else {
|
} else {
|
||||||
buttonString = "Sell"
|
buttonString = "Sell"
|
||||||
}
|
}
|
||||||
|
} else if case .paidMessages = component.mode {
|
||||||
|
//TODO:localize
|
||||||
|
buttonString = "OK"
|
||||||
} else if let amount = state.amount {
|
} else if let amount = state.amount {
|
||||||
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
||||||
} else {
|
} else {
|
||||||
@ -432,6 +457,8 @@ private final class SheetContent: CombinedComponent {
|
|||||||
amount = nil
|
amount = nil
|
||||||
case .starGiftResell:
|
case .starGiftResell:
|
||||||
amount = nil
|
amount = nil
|
||||||
|
case let .paidMessages(initialValue):
|
||||||
|
amount = StarsAmount(value: initialValue, nanos: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
@ -553,6 +580,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||||||
case paidMedia(Int64?)
|
case paidMedia(Int64?)
|
||||||
case reaction(Int64?)
|
case reaction(Int64?)
|
||||||
case starGiftResell(Bool)
|
case starGiftResell(Bool)
|
||||||
|
case paidMessages(Int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
@ -3664,8 +3664,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return StarsWithdrawScreen(context: context, mode: .withdraw(stats), completion: completion)
|
return StarsWithdrawScreen(context: context, mode: .withdraw(stats), completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController {
|
public func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject, completion: @escaping (Int64) -> Void) -> ViewController {
|
||||||
return StarsWithdrawScreen(context: context, mode: .accountWithdraw, completion: completion)
|
let mode: StarsWithdrawScreen.Mode
|
||||||
|
switch subject {
|
||||||
|
case .withdraw:
|
||||||
|
mode = .accountWithdraw
|
||||||
|
case let .enterAmount(current):
|
||||||
|
mode = .paidMessages(current.value)
|
||||||
|
}
|
||||||
|
return StarsWithdrawScreen(context: context, mode: mode, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController {
|
public func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController {
|
||||||
|
107
submodules/ffmpeg/BUILD
vendored
107
submodules/ffmpeg/BUILD
vendored
@ -1,111 +1,4 @@
|
|||||||
|
|
||||||
'''ffmpeg_header_paths = [
|
|
||||||
"libavutil/hwcontext.h",
|
|
||||||
"libavutil/time.h",
|
|
||||||
"libavutil/hwcontext_cuda.h",
|
|
||||||
"libavutil/intfloat.h",
|
|
||||||
"libavutil/error.h",
|
|
||||||
"libavutil/fifo.h",
|
|
||||||
"libavutil/blowfish.h",
|
|
||||||
"libavutil/hwcontext_mediacodec.h",
|
|
||||||
"libavutil/replaygain.h",
|
|
||||||
"libavutil/version.h",
|
|
||||||
"libavutil/murmur3.h",
|
|
||||||
"libavutil/stereo3d.h",
|
|
||||||
"libavutil/samplefmt.h",
|
|
||||||
"libavutil/pixdesc.h",
|
|
||||||
"libavutil/base64.h",
|
|
||||||
"libavutil/rational.h",
|
|
||||||
"libavutil/sha.h",
|
|
||||||
"libavutil/motion_vector.h",
|
|
||||||
"libavutil/avconfig.h",
|
|
||||||
"libavutil/lfg.h",
|
|
||||||
"libavutil/avutil.h",
|
|
||||||
"libavutil/xtea.h",
|
|
||||||
"libavutil/crc.h",
|
|
||||||
"libavutil/hwcontext_vdpau.h",
|
|
||||||
"libavutil/frame.h",
|
|
||||||
"libavutil/file.h",
|
|
||||||
"libavutil/md5.h",
|
|
||||||
"libavutil/cast5.h",
|
|
||||||
"libavutil/hwcontext_vaapi.h",
|
|
||||||
"libavutil/spherical.h",
|
|
||||||
"libavutil/ffversion.h",
|
|
||||||
"libavutil/audio_fifo.h",
|
|
||||||
"libavutil/tree.h",
|
|
||||||
"libavutil/threadmessage.h",
|
|
||||||
"libavutil/attributes.h",
|
|
||||||
"libavutil/adler32.h",
|
|
||||||
"libavutil/hwcontext_d3d11va.h",
|
|
||||||
"libavutil/timecode.h",
|
|
||||||
"libavutil/sha512.h",
|
|
||||||
"libavutil/hwcontext_dxva2.h",
|
|
||||||
"libavutil/display.h",
|
|
||||||
"libavutil/buffer.h",
|
|
||||||
"libavutil/camellia.h",
|
|
||||||
"libavutil/pixelutils.h",
|
|
||||||
"libavutil/hwcontext_drm.h",
|
|
||||||
"libavutil/common.h",
|
|
||||||
"libavutil/hmac.h",
|
|
||||||
"libavutil/eval.h",
|
|
||||||
"libavutil/dict.h",
|
|
||||||
"libavutil/random_seed.h",
|
|
||||||
"libavutil/opt.h",
|
|
||||||
"libavutil/mastering_display_metadata.h",
|
|
||||||
"libavutil/log.h",
|
|
||||||
"libavutil/aes.h",
|
|
||||||
"libavutil/macros.h",
|
|
||||||
"libavutil/bswap.h",
|
|
||||||
"libavutil/rc4.h",
|
|
||||||
"libavutil/tea.h",
|
|
||||||
"libavutil/cpu.h",
|
|
||||||
"libavutil/lzo.h",
|
|
||||||
"libavutil/des.h",
|
|
||||||
"libavutil/channel_layout.h",
|
|
||||||
"libavutil/encryption_info.h",
|
|
||||||
"libavutil/twofish.h",
|
|
||||||
"libavutil/imgutils.h",
|
|
||||||
"libavutil/hwcontext_videotoolbox.h",
|
|
||||||
"libavutil/mem.h",
|
|
||||||
"libavutil/parseutils.h",
|
|
||||||
"libavutil/ripemd.h",
|
|
||||||
"libavutil/bprint.h",
|
|
||||||
"libavutil/hwcontext_qsv.h",
|
|
||||||
"libavutil/pixfmt.h",
|
|
||||||
"libavutil/aes_ctr.h",
|
|
||||||
"libavutil/timestamp.h",
|
|
||||||
"libavutil/downmix_info.h",
|
|
||||||
"libavutil/avassert.h",
|
|
||||||
"libavutil/hash.h",
|
|
||||||
"libavutil/mathematics.h",
|
|
||||||
"libavutil/intreadwrite.h",
|
|
||||||
"libavutil/avstring.h",
|
|
||||||
"libavformat/version.h",
|
|
||||||
"libavformat/avio.h",
|
|
||||||
"libavformat/avformat.h",
|
|
||||||
"libavcodec/adts_parser.h",
|
|
||||||
"libavcodec/avcodec.h",
|
|
||||||
"libavcodec/version.h",
|
|
||||||
"libavcodec/vdpau.h",
|
|
||||||
"libavcodec/qsv.h",
|
|
||||||
"libavcodec/vaapi.h",
|
|
||||||
"libavcodec/videotoolbox.h",
|
|
||||||
"libavcodec/xvmc.h",
|
|
||||||
"libavcodec/mediacodec.h",
|
|
||||||
"libavcodec/d3d11va.h",
|
|
||||||
"libavcodec/avfft.h",
|
|
||||||
"libavcodec/jni.h",
|
|
||||||
"libavcodec/dirac.h",
|
|
||||||
"libavcodec/avdct.h",
|
|
||||||
"libavcodec/ac3_parser.h",
|
|
||||||
"libavcodec/vorbis_parser.h",
|
|
||||||
"libavcodec/dxva2.h",
|
|
||||||
"libavcodec/dv_profile.h",
|
|
||||||
"libswresample/version.h",
|
|
||||||
"libswresample/swresample.h",
|
|
||||||
]'''
|
|
||||||
|
|
||||||
|
|
||||||
ffmpeg_header_paths = [
|
ffmpeg_header_paths = [
|
||||||
"libavutil/hwcontext.h",
|
"libavutil/hwcontext.h",
|
||||||
"libavutil/time.h",
|
"libavutil/time.h",
|
||||||
|
@ -37,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
- (NSData *)emojiState;
|
- (NSData *)emojiState;
|
||||||
- (NSArray<TdCallParticipant *> *)participants;
|
- (NSArray<TdCallParticipant *> *)participants;
|
||||||
|
|
||||||
|
- (NSDictionary<NSNumber *, NSNumber *> *)participantLatencies;
|
||||||
|
|
||||||
- (void)applyBlock:(NSData *)block;
|
- (void)applyBlock:(NSData *)block;
|
||||||
- (void)applyBroadcastBlock:(NSData *)block;
|
- (void)applyBroadcastBlock:(NSData *)block;
|
||||||
|
|
||||||
|
46
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
46
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
@ -190,6 +190,52 @@ static NSString *hexStringFromData(NSData *data) {
|
|||||||
return participants;
|
return participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSDictionary<NSNumber *, NSNumber *> *)participantLatencies {
|
||||||
|
auto describeResult = tde2e_api::call_describe(_callId);
|
||||||
|
if (describeResult.is_ok()) {
|
||||||
|
NSString *string = [[NSString alloc] initWithData:[NSData dataWithBytes:describeResult.value().data() length:describeResult.value().size()] encoding:NSASCIIStringEncoding];
|
||||||
|
|
||||||
|
NSRegularExpression *pairRe = [NSRegularExpression regularExpressionWithPattern:@"(\\d+):(\\d+\\.\\d+)s" options:0 error:NULL];
|
||||||
|
|
||||||
|
NSMutableDictionary<NSNumber*, NSNumber*> *commitTimes = [NSMutableDictionary dictionary];
|
||||||
|
NSMutableDictionary<NSNumber*, NSNumber*> *revealTimes = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
|
// split into lines and look for the two lines
|
||||||
|
[string enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) {
|
||||||
|
if ([line containsString:@"commit ="]) {
|
||||||
|
[pairRe enumerateMatchesInString:line options:0 range:NSMakeRange(0, line.length) usingBlock:^(NSTextCheckingResult * _Nullable match, NSMatchingFlags flags, BOOL * _Nonnull stop) {
|
||||||
|
NSString *userIdStr = [line substringWithRange:[match rangeAtIndex:1]];
|
||||||
|
NSString *durStr = [line substringWithRange:[match rangeAtIndex:2]];
|
||||||
|
NSNumber *uid = @([userIdStr longLongValue]);
|
||||||
|
NSNumber *dur = @([durStr doubleValue]);
|
||||||
|
commitTimes[uid] = dur;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
else if ([line containsString:@"reveal ="]) {
|
||||||
|
[pairRe enumerateMatchesInString:line options:0 range:NSMakeRange(0, line.length) usingBlock:^(NSTextCheckingResult * _Nullable match, NSMatchingFlags flags, BOOL * _Nonnull stop) {
|
||||||
|
NSString *userIdStr = [line substringWithRange:[match rangeAtIndex:1]];
|
||||||
|
NSString *durStr = [line substringWithRange:[match rangeAtIndex:2]];
|
||||||
|
NSNumber *uid = @([userIdStr longLongValue]);
|
||||||
|
NSNumber *dur = @([durStr doubleValue]);
|
||||||
|
revealTimes[uid] = dur;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
// build final result = commit+reveal
|
||||||
|
NSMutableDictionary<NSNumber*, NSNumber*> *result = [NSMutableDictionary dictionary];
|
||||||
|
for (NSNumber *uid in commitTimes) {
|
||||||
|
double commit = commitTimes[uid].doubleValue;
|
||||||
|
double reveal = revealTimes[uid].doubleValue; // will be 0 if missing
|
||||||
|
result[uid] = @(commit + reveal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{};
|
||||||
|
}
|
||||||
|
|
||||||
- (void)applyBlock:(NSData *)block {
|
- (void)applyBlock:(NSData *)block {
|
||||||
std::string mappedBlock((uint8_t *)block.bytes, ((uint8_t *)block.bytes) + block.length);
|
std::string mappedBlock((uint8_t *)block.bytes, ((uint8_t *)block.bytes) + block.length);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user