mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
bebb448ffb
@ -23,7 +23,6 @@ internal:
|
|||||||
- export PATH=`gem environment gemdir`/bin:$PATH
|
- export PATH=`gem environment gemdir`/bin:$PATH
|
||||||
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64
|
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64
|
||||||
- python3 -u build-system/Make/DeployToFirebase.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/firebase-configurations/firebase-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
- python3 -u build-system/Make/DeployToFirebase.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/firebase-configurations/firebase-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
||||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
|
||||||
environment:
|
environment:
|
||||||
name: internal
|
name: internal
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -56,7 +55,6 @@ appstore_development:
|
|||||||
- tags
|
- tags
|
||||||
script:
|
script:
|
||||||
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
||||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appstore-development.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
|
||||||
environment:
|
environment:
|
||||||
name: appstore-development
|
name: appstore-development
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -76,7 +74,6 @@ experimental_i:
|
|||||||
- export PATH=/opt/homebrew/opt/ruby/bin:$PATH
|
- export PATH=/opt/homebrew/opt/ruby/bin:$PATH
|
||||||
- export PATH=`gem environment gemdir`/bin:$PATH
|
- export PATH=`gem environment gemdir`/bin:$PATH
|
||||||
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64
|
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64
|
||||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
|
||||||
environment:
|
environment:
|
||||||
name: experimental
|
name: experimental
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -94,7 +91,6 @@ experimental:
|
|||||||
- tags
|
- tags
|
||||||
script:
|
script:
|
||||||
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
||||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental2.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
|
||||||
environment:
|
environment:
|
||||||
name: experimental-2
|
name: experimental-2
|
||||||
artifacts:
|
artifacts:
|
||||||
|
@ -157,4 +157,5 @@ public protocol ContactMultiselectionController: ViewController {
|
|||||||
var result: Signal<ContactMultiselectionResult, NoError> { get }
|
var result: Signal<ContactMultiselectionResult, NoError> { get }
|
||||||
var displayProgress: Bool { get set }
|
var displayProgress: Bool { get set }
|
||||||
var dismissed: (() -> Void)? { get set }
|
var dismissed: (() -> Void)? { get set }
|
||||||
|
var isCallVideoOptionSelected: Bool { get }
|
||||||
}
|
}
|
||||||
|
@ -704,7 +704,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.conferenceAvatarListNode = conferenceAvatarListNode
|
strongSelf.conferenceAvatarListNode = conferenceAvatarListNode
|
||||||
strongSelf.containerNode.addSubnode(conferenceAvatarListNode)
|
strongSelf.containerNode.addSubnode(conferenceAvatarListNode)
|
||||||
}
|
}
|
||||||
let avatarListContents = conferenceAvatarListContext.update(peers: conferenceAvatars, animated: false)
|
let avatarListContents = conferenceAvatarListContext.update(peers: Array(conferenceAvatars.prefix(3)), animated: false)
|
||||||
let avatarListSize = conferenceAvatarListNode.update(context: item.context, content: avatarListContents, itemSize: CGSize(width: CGFloat(multipleAvatarDiameter), height: CGFloat(multipleAvatarDiameter)), customSpacing: multipleAvatarDiameter - 8.0, font: multipleAvatarFont, animated: false, synchronousLoad: synchronousLoads)
|
let avatarListSize = conferenceAvatarListNode.update(context: item.context, content: avatarListContents, itemSize: CGSize(width: CGFloat(multipleAvatarDiameter), height: CGFloat(multipleAvatarDiameter)), customSpacing: multipleAvatarDiameter - 8.0, font: multipleAvatarFont, animated: false, synchronousLoad: synchronousLoads)
|
||||||
conferenceAvatarListNode.frame = CGRect(origin: CGPoint(x: avatarFrame.minX + floor((avatarFrame.width - avatarListSize.width) / 2.0), y: avatarFrame.minY + floor((avatarFrame.height - avatarListSize.height) / 2.0)), size: avatarListSize)
|
conferenceAvatarListNode.frame = CGRect(origin: CGPoint(x: avatarFrame.minX + floor((avatarFrame.width - avatarListSize.width) / 2.0), y: avatarFrame.minY + floor((avatarFrame.height - avatarListSize.height) / 2.0)), size: avatarListSize)
|
||||||
} else {
|
} else {
|
||||||
|
@ -208,7 +208,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createGroupCall(peerIds: [EnginePeer.Id], completion: (() -> Void)? = nil) {
|
private func createGroupCall(peerIds: [EnginePeer.Id], isVideo: Bool, completion: (() -> Void)? = nil) {
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
|
|
||||||
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
||||||
@ -274,7 +274,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
isStream: false
|
isStream: false
|
||||||
),
|
),
|
||||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||||
beginWithVideo: false,
|
beginWithVideo: isVideo,
|
||||||
invitePeerIds: peerIds
|
invitePeerIds: peerIds
|
||||||
)
|
)
|
||||||
completion?()
|
completion?()
|
||||||
@ -401,7 +401,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}
|
}
|
||||||
}, createGroupCall: { [weak self] in
|
}, createGroupCall: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.createGroupCall(peerIds: [])
|
strongSelf.createGroupCall(peerIds: [], isVideo: false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -520,7 +520,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.createGroupCall(peerIds: [])
|
self.createGroupCall(peerIds: [], isVideo: false)
|
||||||
}, clearHighlightAutomatically: true)]
|
}, clearHighlightAutomatically: true)]
|
||||||
|
|
||||||
let controller = self.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(
|
let controller = self.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(
|
||||||
@ -565,11 +565,12 @@ public final class CallListController: TelegramBaseController {
|
|||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isVideo = controller?.isCallVideoOptionSelected ?? false
|
||||||
|
|
||||||
if peerIds.count == 1 {
|
if peerIds.count == 1 {
|
||||||
//TODO:release isVideo
|
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
self.call(peerIds[0], isVideo: false, began: { [weak self] in
|
self.call(peerIds[0], isVideo: isVideo, began: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
@ -586,7 +587,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.createGroupCall(peerIds: peerIds, completion: {
|
self.createGroupCall(peerIds: peerIds, isVideo: isVideo, completion: {
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -27,15 +27,19 @@ public func presentationStringsFormattedNumber(_ count: Int32, _ groupingSeparat
|
|||||||
if groupingSeparator.isEmpty || abs(count) < 1000 {
|
if groupingSeparator.isEmpty || abs(count) < 1000 {
|
||||||
return string
|
return string
|
||||||
} else {
|
} else {
|
||||||
var groupedString: String = ""
|
if count < 0 {
|
||||||
for i in 0 ..< Int(ceil(Double(string.count) / 3.0)) {
|
return "-\(presentationStringsFormattedNumber(abs(count), groupingSeparator))"
|
||||||
let index = string.count - Int(i + 1) * 3
|
} else {
|
||||||
if !groupedString.isEmpty {
|
var groupedString: String = ""
|
||||||
groupedString = groupingSeparator + groupedString
|
for i in 0 ..< Int(ceil(Double(string.count) / 3.0)) {
|
||||||
|
let index = string.count - Int(i + 1) * 3
|
||||||
|
if !groupedString.isEmpty {
|
||||||
|
groupedString = groupingSeparator + groupedString
|
||||||
|
}
|
||||||
|
groupedString = String(string[string.index(string.startIndex, offsetBy: max(0, index)) ..< string.index(string.startIndex, offsetBy: index + 3)]) + groupedString
|
||||||
}
|
}
|
||||||
groupedString = String(string[string.index(string.startIndex, offsetBy: max(0, index)) ..< string.index(string.startIndex, offsetBy: index + 3)]) + groupedString
|
return groupedString
|
||||||
}
|
}
|
||||||
return groupedString
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +86,7 @@ swift_library(
|
|||||||
"//submodules/UndoUI",
|
"//submodules/UndoUI",
|
||||||
"//submodules/ContextUI",
|
"//submodules/ContextUI",
|
||||||
"//submodules/AvatarNode",
|
"//submodules/AvatarNode",
|
||||||
|
"//submodules/TelegramUI/Components/Utils/AnimatableProperty",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -4,125 +4,7 @@ import MetalKit
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import Display
|
import Display
|
||||||
import MetalImageView
|
import MetalImageView
|
||||||
|
import AnimatableProperty
|
||||||
private final class PropertyAnimation<T: Interpolatable> {
|
|
||||||
let from: T
|
|
||||||
let to: T
|
|
||||||
let animation: ComponentTransition.Animation
|
|
||||||
let startTimestamp: Double
|
|
||||||
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
|
|
||||||
|
|
||||||
init(fromValue: T, toValue: T, animation: ComponentTransition.Animation, startTimestamp: Double) {
|
|
||||||
self.from = fromValue
|
|
||||||
self.to = toValue
|
|
||||||
self.animation = animation
|
|
||||||
self.startTimestamp = startTimestamp
|
|
||||||
self.interpolator = T.interpolator()
|
|
||||||
}
|
|
||||||
|
|
||||||
func valueAt(_ t: CGFloat) -> Interpolatable {
|
|
||||||
if t <= 0.0 {
|
|
||||||
return self.from
|
|
||||||
} else if t >= 1.0 {
|
|
||||||
return self.to
|
|
||||||
} else {
|
|
||||||
return self.interpolator(self.from, self.to, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class AnimatableProperty<T: Interpolatable> {
|
|
||||||
var presentationValue: T
|
|
||||||
var value: T
|
|
||||||
private var animation: PropertyAnimation<T>?
|
|
||||||
|
|
||||||
init(value: T) {
|
|
||||||
self.value = value
|
|
||||||
self.presentationValue = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(value: T, transition: ComponentTransition = .immediate) {
|
|
||||||
let currentTimestamp = CACurrentMediaTime()
|
|
||||||
if case .none = transition.animation {
|
|
||||||
if let animation = self.animation, case let .curve(duration, curve) = animation.animation {
|
|
||||||
self.value = value
|
|
||||||
let elapsed = duration - (currentTimestamp - animation.startTimestamp)
|
|
||||||
if let presentationValue = self.presentationValue as? CGFloat, let newValue = value as? CGFloat, abs(presentationValue - newValue) > 0.56 {
|
|
||||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed * 0.8, curve: curve), startTimestamp: currentTimestamp)
|
|
||||||
} else {
|
|
||||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed, curve: curve), startTimestamp: currentTimestamp)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.value = value
|
|
||||||
self.presentationValue = value
|
|
||||||
self.animation = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.value = value
|
|
||||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: transition.animation, startTimestamp: currentTimestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tick(timestamp: Double) -> Bool {
|
|
||||||
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeFromStart = timestamp - animation.startTimestamp
|
|
||||||
var t = max(0.0, timeFromStart / duration)
|
|
||||||
switch curve {
|
|
||||||
case .linear:
|
|
||||||
break
|
|
||||||
case .easeInOut:
|
|
||||||
t = listViewAnimationCurveEaseInOut(t)
|
|
||||||
case .spring:
|
|
||||||
t = lookupSpringValue(t)
|
|
||||||
case let .custom(x1, y1, x2, y2):
|
|
||||||
t = bezierPoint(CGFloat(x1), CGFloat(y1), CGFloat(x2), CGFloat(y2), t)
|
|
||||||
}
|
|
||||||
self.presentationValue = animation.valueAt(t) as! T
|
|
||||||
|
|
||||||
if timeFromStart <= duration {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
self.animation = nil
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func lookupSpringValue(_ t: CGFloat) -> CGFloat {
|
|
||||||
let table: [(CGFloat, CGFloat)] = [
|
|
||||||
(0.0, 0.0),
|
|
||||||
(0.0625, 0.1123005598783493),
|
|
||||||
(0.125, 0.31598418951034546),
|
|
||||||
(0.1875, 0.5103585720062256),
|
|
||||||
(0.25, 0.6650152802467346),
|
|
||||||
(0.3125, 0.777747631072998),
|
|
||||||
(0.375, 0.8557760119438171),
|
|
||||||
(0.4375, 0.9079672694206238),
|
|
||||||
(0.5, 0.942038357257843),
|
|
||||||
(0.5625, 0.9638798832893372),
|
|
||||||
(0.625, 0.9776856303215027),
|
|
||||||
(0.6875, 0.9863143563270569),
|
|
||||||
(0.75, 0.991658091545105),
|
|
||||||
(0.8125, 0.9949421286582947),
|
|
||||||
(0.875, 0.9969474077224731),
|
|
||||||
(0.9375, 0.9981651306152344),
|
|
||||||
(1.0, 1.0)
|
|
||||||
]
|
|
||||||
|
|
||||||
for i in 0 ..< table.count - 2 {
|
|
||||||
let lhs = table[i]
|
|
||||||
let rhs = table[i + 1]
|
|
||||||
|
|
||||||
if t >= lhs.0 && t <= rhs.0 {
|
|
||||||
let fraction = (t - lhs.0) / (rhs.0 - lhs.0)
|
|
||||||
let value = lhs.1 + fraction * (rhs.1 - lhs.1)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ShutterBlobLayer: MetalImageLayer {
|
private class ShutterBlobLayer: MetalImageLayer {
|
||||||
override public init() {
|
override public init() {
|
||||||
|
@ -323,7 +323,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.addSubnode(peopleAvatarsNode)
|
strongSelf.addSubnode(peopleAvatarsNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
let peopleAvatarsContent = peopleAvatarsContext.update(peers: peopleAvatars.map(EnginePeer.init), animated: false)
|
let peopleAvatarsContent = peopleAvatarsContext.update(peers: peopleAvatars.prefix(3).map(EnginePeer.init), animated: false)
|
||||||
let peopleAvatarsSize = peopleAvatarsNode.update(context: item.context, content: peopleAvatarsContent, itemSize: CGSize(width: peopleAvatarSize, height: peopleAvatarSize), customSpacing: peopleAvatarSize - peopleAvatarSpacing, font: avatarFont, animated: false, synchronousLoad: false)
|
let peopleAvatarsSize = peopleAvatarsNode.update(context: item.context, content: peopleAvatarsContent, itemSize: CGSize(width: peopleAvatarSize, height: peopleAvatarSize), customSpacing: peopleAvatarSize - peopleAvatarSpacing, font: avatarFont, animated: false, synchronousLoad: false)
|
||||||
peopleAvatarsNode.frame = CGRect(origin: CGPoint(x: labelFrame.maxX + avatarsLeftInset, y: labelFrame.minY - 1.0), size: peopleAvatarsSize)
|
peopleAvatarsNode.frame = CGRect(origin: CGPoint(x: labelFrame.maxX + avatarsLeftInset, y: labelFrame.minY - 1.0), size: peopleAvatarsSize)
|
||||||
} else {
|
} else {
|
||||||
|
@ -504,7 +504,6 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
|
|
||||||
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
|
|
||||||
let _ = (starsContext.onUpdate
|
let _ = (starsContext.onUpdate
|
||||||
|> deliverOnMainQueue).start(next: {
|
|> deliverOnMainQueue).start(next: {
|
||||||
proceed()
|
proceed()
|
||||||
|
@ -32,7 +32,7 @@ final class FlipButtonContentComponent: Component {
|
|||||||
private let icon = SimpleLayer()
|
private let icon = SimpleLayer()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.5), enableBlur: true)
|
self.backgroundView = BlurredBackgroundView(color: UIColor(white: 0.2, alpha: 0.45), enableBlur: true)
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
@ -1001,9 +1001,9 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if let starsState = data.starsState {
|
if let starsState = data.starsState {
|
||||||
if !isPremiumDisabled || starsState.balance > StarsAmount.zero {
|
if !isPremiumDisabled || abs(starsState.balance.value) > 0 {
|
||||||
let balanceText: NSAttributedString
|
let balanceText: NSAttributedString
|
||||||
if starsState.balance > StarsAmount.zero {
|
if abs(starsState.balance.value) > 0 {
|
||||||
let formattedLabel = formatStarsAmountText(starsState.balance, dateTimeFormat: presentationData.dateTimeFormat)
|
let formattedLabel = formatStarsAmountText(starsState.balance, dateTimeFormat: presentationData.dateTimeFormat)
|
||||||
let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0))
|
let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0))
|
||||||
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
|
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
|
||||||
|
@ -69,6 +69,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
"//submodules/Components/HierarchyTrackingLayer",
|
"//submodules/Components/HierarchyTrackingLayer",
|
||||||
"//submodules/TelegramUI/Components/ChatListTitleView",
|
"//submodules/TelegramUI/Components/ChatListTitleView",
|
||||||
|
"//submodules/TelegramUI/Components/Utils/AnimatableProperty",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -87,7 +87,7 @@ fragment half4 cameraBlobFragment(
|
|||||||
} else if (primaryParameters.y < 0) {
|
} else if (primaryParameters.y < 0) {
|
||||||
float cutRadius = abs(primaryParameters.y);
|
float cutRadius = abs(primaryParameters.y);
|
||||||
float2 primaryFeatheredOffset = primaryOffset;
|
float2 primaryFeatheredOffset = primaryOffset;
|
||||||
primaryFeatheredOffset.x *= 1.129;
|
primaryFeatheredOffset.x *= 1.131;
|
||||||
|
|
||||||
float distFromCenter = length(uv - primaryFeatheredOffset);
|
float distFromCenter = length(uv - primaryFeatheredOffset);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import MetalKit
|
|||||||
import MetalEngine
|
import MetalEngine
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import AnimatableProperty
|
||||||
|
|
||||||
private var metalLibraryValue: MTLLibrary?
|
private var metalLibraryValue: MTLLibrary?
|
||||||
func metalLibrary(device: MTLDevice) -> MTLLibrary? {
|
func metalLibrary(device: MTLDevice) -> MTLLibrary? {
|
||||||
@ -28,124 +28,7 @@ func metalLibrary(device: MTLDevice) -> MTLLibrary? {
|
|||||||
return library
|
return library
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class PropertyAnimation<T: Interpolatable> {
|
|
||||||
let from: T
|
|
||||||
let to: T
|
|
||||||
let animation: ComponentTransition.Animation
|
|
||||||
let startTimestamp: Double
|
|
||||||
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
|
|
||||||
|
|
||||||
init(fromValue: T, toValue: T, animation: ComponentTransition.Animation, startTimestamp: Double) {
|
|
||||||
self.from = fromValue
|
|
||||||
self.to = toValue
|
|
||||||
self.animation = animation
|
|
||||||
self.startTimestamp = startTimestamp
|
|
||||||
self.interpolator = T.interpolator()
|
|
||||||
}
|
|
||||||
|
|
||||||
func valueAt(_ t: CGFloat) -> Interpolatable {
|
|
||||||
if t <= 0.0 {
|
|
||||||
return self.from
|
|
||||||
} else if t >= 1.0 {
|
|
||||||
return self.to
|
|
||||||
} else {
|
|
||||||
return self.interpolator(self.from, self.to, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class AnimatableProperty<T: Interpolatable> {
|
|
||||||
var presentationValue: T
|
|
||||||
var value: T
|
|
||||||
private var animation: PropertyAnimation<T>?
|
|
||||||
|
|
||||||
init(value: T) {
|
|
||||||
self.value = value
|
|
||||||
self.presentationValue = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(value: T, transition: ComponentTransition = .immediate) {
|
|
||||||
let currentTimestamp = CACurrentMediaTime()
|
|
||||||
if case .none = transition.animation {
|
|
||||||
if let animation = self.animation, case let .curve(duration, curve) = animation.animation {
|
|
||||||
self.value = value
|
|
||||||
let elapsed = duration - (currentTimestamp - animation.startTimestamp)
|
|
||||||
if let presentationValue = self.presentationValue as? CGFloat, let newValue = value as? CGFloat, abs(presentationValue - newValue) > 0.56 {
|
|
||||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed * 0.8, curve: curve), startTimestamp: currentTimestamp)
|
|
||||||
} else {
|
|
||||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed, curve: curve), startTimestamp: currentTimestamp)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.value = value
|
|
||||||
self.presentationValue = value
|
|
||||||
self.animation = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.value = value
|
|
||||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: transition.animation, startTimestamp: currentTimestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tick(timestamp: Double) -> Bool {
|
|
||||||
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeFromStart = timestamp - animation.startTimestamp
|
|
||||||
var t = max(0.0, timeFromStart / duration)
|
|
||||||
switch curve {
|
|
||||||
case .linear:
|
|
||||||
break
|
|
||||||
case .easeInOut:
|
|
||||||
t = listViewAnimationCurveEaseInOut(t)
|
|
||||||
case .spring:
|
|
||||||
t = lookupSpringValue(t)
|
|
||||||
case let .custom(x1, y1, x2, y2):
|
|
||||||
t = bezierPoint(CGFloat(x1), CGFloat(y1), CGFloat(x2), CGFloat(y2), t)
|
|
||||||
}
|
|
||||||
self.presentationValue = animation.valueAt(t) as! T
|
|
||||||
|
|
||||||
if timeFromStart <= duration {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
self.animation = nil
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func lookupSpringValue(_ t: CGFloat) -> CGFloat {
|
|
||||||
let table: [(CGFloat, CGFloat)] = [
|
|
||||||
(0.0, 0.0),
|
|
||||||
(0.0625, 0.1123005598783493),
|
|
||||||
(0.125, 0.31598418951034546),
|
|
||||||
(0.1875, 0.5103585720062256),
|
|
||||||
(0.25, 0.6650152802467346),
|
|
||||||
(0.3125, 0.777747631072998),
|
|
||||||
(0.375, 0.8557760119438171),
|
|
||||||
(0.4375, 0.9079672694206238),
|
|
||||||
(0.5, 0.942038357257843),
|
|
||||||
(0.5625, 0.9638798832893372),
|
|
||||||
(0.625, 0.9776856303215027),
|
|
||||||
(0.6875, 0.9863143563270569),
|
|
||||||
(0.75, 0.991658091545105),
|
|
||||||
(0.8125, 0.9949421286582947),
|
|
||||||
(0.875, 0.9969474077224731),
|
|
||||||
(0.9375, 0.9981651306152344),
|
|
||||||
(1.0, 1.0)
|
|
||||||
]
|
|
||||||
|
|
||||||
for i in 0 ..< table.count - 2 {
|
|
||||||
let lhs = table[i]
|
|
||||||
let rhs = table[i + 1]
|
|
||||||
|
|
||||||
if t >= lhs.0 && t <= rhs.0 {
|
|
||||||
let fraction = (t - lhs.0) / (rhs.0 - lhs.0)
|
|
||||||
let value = lhs.1 + fraction * (rhs.1 - lhs.1)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
final class StoryBlobLayer: MetalEngineSubjectLayer, MetalEngineSubject {
|
final class StoryBlobLayer: MetalEngineSubjectLayer, MetalEngineSubject {
|
||||||
var internalData: MetalEngineSubjectInternalData?
|
var internalData: MetalEngineSubjectInternalData?
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "AnimatableProperty",
|
||||||
|
module_name = "AnimatableProperty",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,123 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
|
||||||
|
private final class PropertyAnimation<T: Interpolatable> {
|
||||||
|
let from: T
|
||||||
|
let to: T
|
||||||
|
let animation: ComponentTransition.Animation
|
||||||
|
let startTimestamp: Double
|
||||||
|
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
|
||||||
|
|
||||||
|
init(fromValue: T, toValue: T, animation: ComponentTransition.Animation, startTimestamp: Double) {
|
||||||
|
self.from = fromValue
|
||||||
|
self.to = toValue
|
||||||
|
self.animation = animation
|
||||||
|
self.startTimestamp = startTimestamp
|
||||||
|
self.interpolator = T.interpolator()
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueAt(_ t: CGFloat) -> Interpolatable {
|
||||||
|
if t <= 0.0 {
|
||||||
|
return self.from
|
||||||
|
} else if t >= 1.0 {
|
||||||
|
return self.to
|
||||||
|
} else {
|
||||||
|
return self.interpolator(self.from, self.to, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class AnimatableProperty<T: Interpolatable> {
|
||||||
|
public private(set) var presentationValue: T
|
||||||
|
public private(set) var value: T
|
||||||
|
private var animation: PropertyAnimation<T>?
|
||||||
|
|
||||||
|
public init(value: T) {
|
||||||
|
self.value = value
|
||||||
|
self.presentationValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(value: T, transition: ComponentTransition = .immediate) {
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if case .none = transition.animation {
|
||||||
|
if let animation = self.animation, case let .curve(duration, curve) = animation.animation {
|
||||||
|
self.value = value
|
||||||
|
let elapsed = duration - (currentTimestamp - animation.startTimestamp)
|
||||||
|
if let presentationValue = self.presentationValue as? CGFloat, let newValue = value as? CGFloat, abs(presentationValue - newValue) > 0.56 {
|
||||||
|
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed * 0.8, curve: curve), startTimestamp: currentTimestamp)
|
||||||
|
} else {
|
||||||
|
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed, curve: curve), startTimestamp: currentTimestamp)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.value = value
|
||||||
|
self.presentationValue = value
|
||||||
|
self.animation = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.value = value
|
||||||
|
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: transition.animation, startTimestamp: currentTimestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func tick(timestamp: Double) -> Bool {
|
||||||
|
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeFromStart = timestamp - animation.startTimestamp
|
||||||
|
var t = max(0.0, timeFromStart / duration)
|
||||||
|
switch curve {
|
||||||
|
case .linear:
|
||||||
|
break
|
||||||
|
case .easeInOut:
|
||||||
|
t = listViewAnimationCurveEaseInOut(t)
|
||||||
|
case .spring:
|
||||||
|
t = lookupSpringValue(t)
|
||||||
|
case let .custom(x1, y1, x2, y2):
|
||||||
|
t = bezierPoint(CGFloat(x1), CGFloat(y1), CGFloat(x2), CGFloat(y2), t)
|
||||||
|
}
|
||||||
|
self.presentationValue = animation.valueAt(t) as! T
|
||||||
|
|
||||||
|
if timeFromStart <= duration {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
self.animation = nil
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func lookupSpringValue(_ t: CGFloat) -> CGFloat {
|
||||||
|
let table: [(CGFloat, CGFloat)] = [
|
||||||
|
(0.0, 0.0),
|
||||||
|
(0.0625, 0.1123005598783493),
|
||||||
|
(0.125, 0.31598418951034546),
|
||||||
|
(0.1875, 0.5103585720062256),
|
||||||
|
(0.25, 0.6650152802467346),
|
||||||
|
(0.3125, 0.777747631072998),
|
||||||
|
(0.375, 0.8557760119438171),
|
||||||
|
(0.4375, 0.9079672694206238),
|
||||||
|
(0.5, 0.942038357257843),
|
||||||
|
(0.5625, 0.9638798832893372),
|
||||||
|
(0.625, 0.9776856303215027),
|
||||||
|
(0.6875, 0.9863143563270569),
|
||||||
|
(0.75, 0.991658091545105),
|
||||||
|
(0.8125, 0.9949421286582947),
|
||||||
|
(0.875, 0.9969474077224731),
|
||||||
|
(0.9375, 0.9981651306152344),
|
||||||
|
(1.0, 1.0)
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in 0 ..< table.count - 2 {
|
||||||
|
let lhs = table[i]
|
||||||
|
let rhs = table[i + 1]
|
||||||
|
|
||||||
|
if t >= lhs.0 && t <= rhs.0 {
|
||||||
|
let fraction = (t - lhs.0) / (rhs.0 - lhs.0)
|
||||||
|
let value = lhs.1 + fraction * (rhs.1 - lhs.1)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0
|
||||||
|
}
|
@ -86,6 +86,13 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||||||
private let onlyWriteable: Bool
|
private let onlyWriteable: Bool
|
||||||
private let isGroupInvitation: Bool
|
private let isGroupInvitation: Bool
|
||||||
private let limit: Int32?
|
private let limit: Int32?
|
||||||
|
|
||||||
|
public var isCallVideoOptionSelected: Bool {
|
||||||
|
guard self.displayNode.isNodeLoaded, let displayNode = self.displayNode as? ContactMultiselectionControllerNode else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return displayNode.isCallVideoOptionSelected
|
||||||
|
}
|
||||||
|
|
||||||
init(_ params: ContactMultiselectionControllerParams) {
|
init(_ params: ContactMultiselectionControllerParams) {
|
||||||
self.params = params
|
self.params = params
|
||||||
|
@ -15,6 +15,8 @@ import EditableTokenListNode
|
|||||||
import SolidRoundedButtonNode
|
import SolidRoundedButtonNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import MultilineTextComponent
|
||||||
|
import CheckComponent
|
||||||
|
|
||||||
private struct SearchResultEntry: Identifiable {
|
private struct SearchResultEntry: Identifiable {
|
||||||
let index: Int
|
let index: Int
|
||||||
@ -84,7 +86,9 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
private let onlyWriteable: Bool
|
private let onlyWriteable: Bool
|
||||||
private let isGroupInvitation: Bool
|
private let isGroupInvitation: Bool
|
||||||
|
|
||||||
private var bottomPanel: ComponentView<Empty>?
|
var isCallVideoOptionSelected: Bool {
|
||||||
|
return self.footerPanelNode?.isCheckOptionSelected ?? false
|
||||||
|
}
|
||||||
|
|
||||||
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
|
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.navigationBar = navigationBar
|
self.navigationBar = navigationBar
|
||||||
@ -119,19 +123,19 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
shortPlaceholder = self.presentationData.strings.Common_Search
|
shortPlaceholder = self.presentationData.strings.Common_Search
|
||||||
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||||
proceedImpl?()
|
proceedImpl?()
|
||||||
})
|
}, checkOptionTitle: nil)
|
||||||
case .requestedUsersSelection:
|
case .requestedUsersSelection:
|
||||||
placeholder = self.presentationData.strings.RequestPeer_SelectUsers_SearchPlaceholder
|
placeholder = self.presentationData.strings.RequestPeer_SelectUsers_SearchPlaceholder
|
||||||
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||||
proceedImpl?()
|
proceedImpl?()
|
||||||
})
|
}, checkOptionTitle: nil)
|
||||||
case let .groupCreation(isCall):
|
case let .groupCreation(isCall):
|
||||||
if isCall {
|
if isCall {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
placeholder = "Search for contacts or usernames"
|
placeholder = "Search for contacts or usernames"
|
||||||
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||||
proceedImpl?()
|
proceedImpl?()
|
||||||
})
|
}, checkOptionTitle: isCall ? "Call with video enabled" : nil)
|
||||||
} else {
|
} else {
|
||||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||||
self.footerPanelNode = nil
|
self.footerPanelNode = nil
|
||||||
@ -554,9 +558,16 @@ private final class FooterPanelNode: ASDisplayNode {
|
|||||||
|
|
||||||
private let theme: PresentationTheme
|
private let theme: PresentationTheme
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
|
private let checkOptionTitle: String?
|
||||||
|
private var checkOptionButton: HighlightTrackingButton?
|
||||||
|
private var checkOptionText: ComponentView<Empty>?
|
||||||
|
private var checkOptionControl: ComponentView<Empty>?
|
||||||
|
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let button: SolidRoundedButtonView
|
private let button: SolidRoundedButtonView
|
||||||
|
|
||||||
|
private(set) var isCheckOptionSelected: Bool = false
|
||||||
|
|
||||||
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
@ -573,14 +584,15 @@ private final class FooterPanelNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) {
|
init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void, checkOptionTitle: String?) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
self.checkOptionTitle = checkOptionTitle
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||||
|
|
||||||
self.button = SolidRoundedButtonView(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 10.0)
|
self.button = SolidRoundedButtonView(theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 10.0)
|
||||||
|
|
||||||
self.content = Content(title: self.strings.Premium_Gift_ContactSelection_Proceed, badge: "")
|
self.content = Content(title: self.strings.Premium_Gift_ContactSelection_Proceed, badge: "")
|
||||||
|
|
||||||
@ -599,9 +611,17 @@ private final class FooterPanelNode: ASDisplayNode {
|
|||||||
super.didLoad()
|
super.didLoad()
|
||||||
self.view.addSubview(self.button)
|
self.view.addSubview(self.button)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func checkOptionButtonPressed() {
|
||||||
|
self.isCheckOptionSelected = !self.isCheckOptionSelected
|
||||||
|
if let validLayout = self.validLayout {
|
||||||
|
let _ = self.updateLayout(width: validLayout.0, sideInset: validLayout.1, bottomInset: validLayout.2, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
self.validLayout = (width, sideInset, bottomInset)
|
self.validLayout = (width, sideInset, bottomInset)
|
||||||
|
|
||||||
let topInset: CGFloat = 9.0
|
let topInset: CGFloat = 9.0
|
||||||
var bottomInset = bottomInset
|
var bottomInset = bottomInset
|
||||||
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
|
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
|
||||||
@ -609,10 +629,104 @@ private final class FooterPanelNode: ASDisplayNode {
|
|||||||
let buttonInset: CGFloat = 16.0 + sideInset
|
let buttonInset: CGFloat = 16.0 + sideInset
|
||||||
let buttonWidth = width - buttonInset * 2.0
|
let buttonWidth = width - buttonInset * 2.0
|
||||||
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
|
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
|
||||||
transition.updateFrame(view: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight))
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
||||||
|
|
||||||
return topInset + buttonHeight + bottomInset
|
var height = topInset + buttonHeight + bottomInset
|
||||||
|
|
||||||
|
var buttonOffset: CGFloat = 0.0
|
||||||
|
if let checkOptionTitle = self.checkOptionTitle {
|
||||||
|
let checkSpacing: CGFloat = 10.0
|
||||||
|
|
||||||
|
let checkOptionButton: HighlightTrackingButton
|
||||||
|
if let current = self.checkOptionButton {
|
||||||
|
checkOptionButton = current
|
||||||
|
} else {
|
||||||
|
checkOptionButton = HighlightTrackingButton()
|
||||||
|
self.checkOptionButton = checkOptionButton
|
||||||
|
self.view.addSubview(checkOptionButton)
|
||||||
|
checkOptionButton.addTarget(self, action: #selector(self.checkOptionButtonPressed), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkOptionText: ComponentView<Empty>
|
||||||
|
if let current = self.checkOptionText {
|
||||||
|
checkOptionText = current
|
||||||
|
} else {
|
||||||
|
checkOptionText = ComponentView()
|
||||||
|
self.checkOptionText = checkOptionText
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkOptionControl: ComponentView<Empty>
|
||||||
|
if let current = self.checkOptionControl {
|
||||||
|
checkOptionControl = current
|
||||||
|
} else {
|
||||||
|
checkOptionControl = ComponentView()
|
||||||
|
self.checkOptionControl = checkOptionControl
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkOptionTextSize = checkOptionText.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: checkOptionTitle, font: Font.regular(13.0), textColor: theme.rootController.navigationBar.primaryTextColor))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: width - sideInset * 2.0 - checkSpacing - 20.0, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let checkTheme = CheckComponent.Theme(
|
||||||
|
backgroundColor: self.theme.list.itemCheckColors.fillColor,
|
||||||
|
strokeColor: self.theme.list.itemCheckColors.foregroundColor,
|
||||||
|
borderColor: self.theme.list.itemCheckColors.strokeColor,
|
||||||
|
overlayBorder: false,
|
||||||
|
hasInset: false,
|
||||||
|
hasShadow: false
|
||||||
|
)
|
||||||
|
let checkOptionControlSize = checkOptionControl.update(
|
||||||
|
transition: transition.isAnimated ? .easeInOut(duration: 0.2) : .immediate,
|
||||||
|
component: AnyComponent(CheckComponent(
|
||||||
|
theme: checkTheme,
|
||||||
|
size: CGSize(width: 18.0, height: 18.0),
|
||||||
|
selected: self.isCheckOptionSelected
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 18.0, height: 18.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let checkContentWidth = checkOptionControlSize.width + checkSpacing + checkOptionTextSize.width
|
||||||
|
let checkContentHeight = 49.0
|
||||||
|
|
||||||
|
let checkOptionControlFrame = CGRect(origin: CGPoint(x: floor((width - checkContentWidth) * 0.5), y: floor(checkContentHeight - checkOptionControlSize.height) * 0.5), size: checkOptionControlSize)
|
||||||
|
let checkOptionTextFrame = CGRect(origin: CGPoint(x: checkOptionControlFrame.maxX + checkSpacing, y: floor((checkContentHeight - checkOptionTextSize.height) * 0.5)), size: checkOptionTextSize)
|
||||||
|
|
||||||
|
if let checkOptionControlView = checkOptionControl.view {
|
||||||
|
if checkOptionControlView.superview == nil {
|
||||||
|
checkOptionControlView.isUserInteractionEnabled = false
|
||||||
|
checkOptionButton.addSubview(checkOptionControlView)
|
||||||
|
}
|
||||||
|
checkOptionControlView.frame = checkOptionControlFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
if let checkOptionTextView = checkOptionText.view {
|
||||||
|
if checkOptionTextView.superview == nil {
|
||||||
|
checkOptionTextView.isUserInteractionEnabled = false
|
||||||
|
checkOptionButton.addSubview(checkOptionTextView)
|
||||||
|
}
|
||||||
|
checkOptionTextView.frame = checkOptionTextFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
checkOptionButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: checkContentHeight))
|
||||||
|
|
||||||
|
height += checkContentHeight
|
||||||
|
buttonOffset += checkContentHeight
|
||||||
|
} else {
|
||||||
|
if let checkOptionButton = self.checkOptionButton {
|
||||||
|
self.checkOptionButton = nil
|
||||||
|
checkOptionButton.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(view: self.button, frame: CGRect(x: buttonInset, y: topInset + buttonOffset, width: buttonWidth, height: buttonHeight))
|
||||||
|
|
||||||
|
return height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1943,7 +1943,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|
|
||||||
let _ = (controller.result
|
let _ = (controller.result
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] result in
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller, weak parentController] result in
|
||||||
|
guard let parentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
guard case let .result(rawPeerIds, _) = result else {
|
guard case let .result(rawPeerIds, _) = result else {
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
return
|
return
|
||||||
|
Loading…
x
Reference in New Issue
Block a user