mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-06 22:33:10 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
# Conflicts: # submodules/TelegramCore/Sources/BotPaymentForm.swift
This commit is contained in:
commit
adc3f4b44a
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -48,9 +48,11 @@ jobs:
|
|||||||
|
|
||||||
cd $SOURCE_DIR
|
cd $SOURCE_DIR
|
||||||
|
|
||||||
|
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
|
||||||
|
|
||||||
export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);')
|
export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);')
|
||||||
export COMMIT_COUNT=$(git rev-list --count HEAD)
|
export COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
export COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
|
export COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
|
||||||
export BUILD_NUMBER="$COMMIT_COUNT"
|
export BUILD_NUMBER="$COMMIT_COUNT"
|
||||||
echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV
|
echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV
|
||||||
echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV
|
echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV
|
||||||
|
|||||||
@ -70,6 +70,7 @@ beta_testflight:
|
|||||||
stage: build
|
stage: build
|
||||||
only:
|
only:
|
||||||
- beta
|
- beta
|
||||||
|
- hotfix
|
||||||
except:
|
except:
|
||||||
- tags
|
- tags
|
||||||
script:
|
script:
|
||||||
@ -87,6 +88,7 @@ deploy_beta_testflight:
|
|||||||
stage: deploy
|
stage: deploy
|
||||||
only:
|
only:
|
||||||
- beta
|
- beta
|
||||||
|
- hotfix
|
||||||
except:
|
except:
|
||||||
- tags
|
- tags
|
||||||
script:
|
script:
|
||||||
@ -100,6 +102,7 @@ verifysanity_beta_testflight:
|
|||||||
stage: verifysanity
|
stage: verifysanity
|
||||||
only:
|
only:
|
||||||
- beta
|
- beta
|
||||||
|
- hotfix
|
||||||
except:
|
except:
|
||||||
- tags
|
- tags
|
||||||
script:
|
script:
|
||||||
@ -118,6 +121,7 @@ verify_beta_testflight:
|
|||||||
stage: verify
|
stage: verify
|
||||||
only:
|
only:
|
||||||
- beta
|
- beta
|
||||||
|
- hotfix
|
||||||
except:
|
except:
|
||||||
- tags
|
- tags
|
||||||
script:
|
script:
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
6098b6ed7c06e42f7bb7226e92744e7951c4c3f89787d702280f907e68a60a15
|
6eb592f57eca2cd3cda976727ba368ed
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_10.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_10.tgs
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_8.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_8.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_9.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_9.tgs
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -6340,11 +6340,15 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceChat.PinVideo" = "Pin Video";
|
"VoiceChat.PinVideo" = "Pin Video";
|
||||||
"VoiceChat.UnpinVideo" = "Unpin Video";
|
"VoiceChat.UnpinVideo" = "Unpin Video";
|
||||||
|
|
||||||
"Notification.VoiceChatScheduled" = "Voice chat scheduled for %@";
|
"Notification.VoiceChatScheduledChannel" = "Voice chat scheduled for %@";
|
||||||
|
"Notification.VoiceChatScheduled" = "%1$@ Voice chat scheduled for %2$@";
|
||||||
|
|
||||||
"VoiceChat.StartsIn" = "Starts in";
|
"VoiceChat.StartsIn" = "Starts in";
|
||||||
"VoiceChat.LateBy" = "Late by";
|
"VoiceChat.LateBy" = "Late by";
|
||||||
|
|
||||||
|
"VoiceChat.StatusStartsIn" = "starts in %@";
|
||||||
|
"VoiceChat.StatusLateBy" = "late by %@";
|
||||||
|
|
||||||
"VoiceChat.StartNow" = "Start Now";
|
"VoiceChat.StartNow" = "Start Now";
|
||||||
"VoiceChat.SetReminder" = "Set Reminder";
|
"VoiceChat.SetReminder" = "Set Reminder";
|
||||||
"VoiceChat.CancelReminder" = "Cancel Reminder";
|
"VoiceChat.CancelReminder" = "Cancel Reminder";
|
||||||
|
|||||||
1
build_number_offset
Normal file
1
build_number_offset
Normal file
@ -0,0 +1 @@
|
|||||||
|
2100
|
||||||
@ -79,7 +79,8 @@ COMMIT_ID="$(git rev-parse HEAD)"
|
|||||||
COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
|
COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
|
||||||
if [ -z "$2" ]; then
|
if [ -z "$2" ]; then
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
|
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
|
||||||
|
COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
|
||||||
BUILD_NUMBER="$COMMIT_COUNT"
|
BUILD_NUMBER="$COMMIT_COUNT"
|
||||||
else
|
else
|
||||||
BUILD_NUMBER="$2"
|
BUILD_NUMBER="$2"
|
||||||
|
|||||||
@ -43,7 +43,7 @@ enum BotCheckoutActionButtonState: Equatable {
|
|||||||
private let titleFont = Font.semibold(17.0)
|
private let titleFont = Font.semibold(17.0)
|
||||||
|
|
||||||
final class BotCheckoutActionButton: HighlightableButtonNode {
|
final class BotCheckoutActionButton: HighlightableButtonNode {
|
||||||
static var diameter: CGFloat = 48.0
|
static var height: CGFloat = 52.0
|
||||||
|
|
||||||
private var inactiveFillColor: UIColor
|
private var inactiveFillColor: UIColor
|
||||||
private var activeFillColor: UIColor
|
private var activeFillColor: UIColor
|
||||||
@ -63,11 +63,13 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
|||||||
self.activeFillColor = activeFillColor
|
self.activeFillColor = activeFillColor
|
||||||
self.foregroundColor = foregroundColor
|
self.foregroundColor = foregroundColor
|
||||||
|
|
||||||
|
let diameter: CGFloat = 20.0
|
||||||
|
|
||||||
self.progressBackgroundNode = ASImageNode()
|
self.progressBackgroundNode = ASImageNode()
|
||||||
self.progressBackgroundNode.displaysAsynchronously = false
|
self.progressBackgroundNode.displaysAsynchronously = false
|
||||||
self.progressBackgroundNode.displayWithoutProcessing = true
|
self.progressBackgroundNode.displayWithoutProcessing = true
|
||||||
self.progressBackgroundNode.isLayerBacked = true
|
self.progressBackgroundNode.isLayerBacked = true
|
||||||
self.progressBackgroundNode.image = generateImage(CGSize(width: BotCheckoutActionButton.diameter, height: BotCheckoutActionButton.diameter), rotatedContext: { size, context in
|
self.progressBackgroundNode.image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
let strokeWidth: CGFloat = 2.0
|
let strokeWidth: CGFloat = 2.0
|
||||||
context.setFillColor(activeFillColor.cgColor)
|
context.setFillColor(activeFillColor.cgColor)
|
||||||
@ -75,7 +77,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
|||||||
|
|
||||||
context.setFillColor(inactiveFillColor.cgColor)
|
context.setFillColor(inactiveFillColor.cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: strokeWidth, y: strokeWidth), size: CGSize(width: size.width - strokeWidth * 2.0, height: size.height - strokeWidth * 2.0)))
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: strokeWidth, y: strokeWidth), size: CGSize(width: size.width - strokeWidth * 2.0, height: size.height - strokeWidth * 2.0)))
|
||||||
let cutout: CGFloat = 10.0
|
let cutout: CGFloat = diameter
|
||||||
context.fill(CGRect(origin: CGPoint(x: floor((size.width - cutout) / 2.0), y: 0.0), size: CGSize(width: cutout, height: cutout)))
|
context.fill(CGRect(origin: CGPoint(x: floor((size.width - cutout) / 2.0), y: 0.0), size: CGSize(width: cutout, height: cutout)))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -83,14 +85,14 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
|||||||
self.inactiveBackgroundNode.displaysAsynchronously = false
|
self.inactiveBackgroundNode.displaysAsynchronously = false
|
||||||
self.inactiveBackgroundNode.displayWithoutProcessing = true
|
self.inactiveBackgroundNode.displayWithoutProcessing = true
|
||||||
self.inactiveBackgroundNode.isLayerBacked = true
|
self.inactiveBackgroundNode.isLayerBacked = true
|
||||||
self.inactiveBackgroundNode.image = generateStretchableFilledCircleImage(diameter: BotCheckoutActionButton.diameter, color: self.foregroundColor, strokeColor: activeFillColor, strokeWidth: 2.0)
|
self.inactiveBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: self.foregroundColor, strokeColor: activeFillColor, strokeWidth: 2.0)
|
||||||
self.inactiveBackgroundNode.alpha = 0.0
|
self.inactiveBackgroundNode.alpha = 0.0
|
||||||
|
|
||||||
self.activeBackgroundNode = ASImageNode()
|
self.activeBackgroundNode = ASImageNode()
|
||||||
self.activeBackgroundNode.displaysAsynchronously = false
|
self.activeBackgroundNode.displaysAsynchronously = false
|
||||||
self.activeBackgroundNode.displayWithoutProcessing = true
|
self.activeBackgroundNode.displayWithoutProcessing = true
|
||||||
self.activeBackgroundNode.isLayerBacked = true
|
self.activeBackgroundNode.isLayerBacked = true
|
||||||
self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: BotCheckoutActionButton.diameter, color: activeFillColor)
|
self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: activeFillColor)
|
||||||
|
|
||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.displaysAsynchronously = false
|
self.labelNode.displaysAsynchronously = false
|
||||||
@ -178,10 +180,21 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
|||||||
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
}
|
}
|
||||||
case .applePay:
|
case .applePay:
|
||||||
if case .applePay = previousState {
|
if self.applePayButton == nil {
|
||||||
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||||
|
let applePayButton: PKPaymentButton
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||||
} else {
|
} else {
|
||||||
|
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||||
|
}
|
||||||
|
applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside)
|
||||||
|
self.view.addSubview(applePayButton)
|
||||||
|
self.applePayButton = applePayButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let applePayButton = self.applePayButton {
|
||||||
|
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: validLayout.width, height: BotCheckoutActionButton.height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -227,14 +240,18 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func applePayButtonPressed() {
|
||||||
|
self.sendActions(forControlEvents: .touchUpInside, with: nil)
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
self.validLayout = size
|
self.validLayout = size
|
||||||
|
|
||||||
transition.updateFrame(node: self.progressBackgroundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - BotCheckoutActionButton.diameter) / 2.0), y: 0.0), size: CGSize(width: BotCheckoutActionButton.diameter, height: BotCheckoutActionButton.diameter)))
|
transition.updateFrame(node: self.progressBackgroundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - BotCheckoutActionButton.height) / 2.0), y: 0.0), size: CGSize(width: BotCheckoutActionButton.height, height: BotCheckoutActionButton.height)))
|
||||||
transition.updateFrame(node: self.inactiveBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter)))
|
transition.updateFrame(node: self.inactiveBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height)))
|
||||||
transition.updateFrame(node: self.activeBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter)))
|
transition.updateFrame(node: self.activeBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height)))
|
||||||
if let applePayButton = self.applePayButton {
|
if let applePayButton = self.applePayButton {
|
||||||
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter))
|
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
var labelSize = self.labelNode.bounds.size
|
var labelSize = self.labelNode.bounds.size
|
||||||
|
|||||||
@ -55,7 +55,7 @@ enum BotCheckoutEntry: ItemListNodeEntry {
|
|||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .header:
|
case .header:
|
||||||
return BotCheckoutSection.header.rawValue
|
return BotCheckoutSection.prices.rawValue
|
||||||
case .price, .tip:
|
case .price, .tip:
|
||||||
return BotCheckoutSection.prices.rawValue
|
return BotCheckoutSection.prices.rawValue
|
||||||
default:
|
default:
|
||||||
@ -286,7 +286,7 @@ private func botCheckoutControllerEntries(presentationData: PresentationData, st
|
|||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
for price in paymentForm.invoice.prices {
|
for price in paymentForm.invoice.prices {
|
||||||
entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: paymentForm.invoice.currency), false, false))
|
entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: paymentForm.invoice.currency), false, index == 0))
|
||||||
totalPrice += price.amount
|
totalPrice += price.amount
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -448,6 +448,8 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
private var currentTipAmount: Int64?
|
private var currentTipAmount: Int64?
|
||||||
private var formRequestDisposable: Disposable?
|
private var formRequestDisposable: Disposable?
|
||||||
|
|
||||||
|
private let actionButtonPanelNode: ASDisplayNode
|
||||||
|
private let actionButtonPanelSeparator: ASDisplayNode
|
||||||
private let actionButton: BotCheckoutActionButton
|
private let actionButton: BotCheckoutActionButton
|
||||||
private let inProgressDimNode: ASDisplayNode
|
private let inProgressDimNode: ASDisplayNode
|
||||||
|
|
||||||
@ -481,13 +483,20 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
|
|
||||||
let signal: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError> = combineLatest(context.sharedContext.presentationData, self.state.get(), paymentFormAndInfo.get(), context.account.postbox.loadedPeerWithId(messageId.peerId))
|
let signal: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError> = combineLatest(context.sharedContext.presentationData, self.state.get(), paymentFormAndInfo.get(), context.account.postbox.loadedPeerWithId(messageId.peerId))
|
||||||
|> map { presentationData, state, paymentFormAndInfo, botPeer -> (ItemListPresentationData, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, paymentFormAndInfo, botPeer -> (ItemListPresentationData, (ItemListNodeState, Any)) in
|
||||||
let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botCheckoutControllerEntries(presentationData: presentationData, state: state, invoice: invoice, paymentForm: paymentFormAndInfo?.0, formInfo: paymentFormAndInfo?.1, validatedFormInfo: paymentFormAndInfo?.2, currentShippingOptionId: paymentFormAndInfo?.3, currentPaymentMethod: paymentFormAndInfo?.4, currentTip: paymentFormAndInfo?.5, botPeer: botPeer), style: .plain, focusItemTag: nil, emptyStateItem: nil, animateChanges: false)
|
let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botCheckoutControllerEntries(presentationData: presentationData, state: state, invoice: invoice, paymentForm: paymentFormAndInfo?.0, formInfo: paymentFormAndInfo?.1, validatedFormInfo: paymentFormAndInfo?.2, currentShippingOptionId: paymentFormAndInfo?.3, currentPaymentMethod: paymentFormAndInfo?.4, currentTip: paymentFormAndInfo?.5, botPeer: botPeer), style: .blocks, focusItemTag: nil, emptyStateItem: nil, animateChanges: false)
|
||||||
|
|
||||||
return (ItemListPresentationData(presentationData), (nodeState, arguments))
|
return (ItemListPresentationData(presentationData), (nodeState, arguments))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.actionButtonPanelNode = ASDisplayNode()
|
||||||
|
self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
|
||||||
|
|
||||||
|
self.actionButtonPanelSeparator = ASDisplayNode()
|
||||||
|
self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||||
|
|
||||||
self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
|
self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||||
self.actionButton.setState(.loading)
|
self.actionButton.setState(.active(""))
|
||||||
|
self.actionButtonPanelNode.isHidden = true
|
||||||
|
|
||||||
self.inProgressDimNode = ASDisplayNode()
|
self.inProgressDimNode = ASDisplayNode()
|
||||||
self.inProgressDimNode.alpha = 0.0
|
self.inProgressDimNode.alpha = 0.0
|
||||||
@ -522,6 +531,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
|
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
|
||||||
strongSelf.currentPaymentMethod = method
|
strongSelf.currentPaymentMethod = method
|
||||||
strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod, strongSelf.currentTipAmount)))
|
strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod, strongSelf.currentTipAmount)))
|
||||||
|
strongSelf.updateActionButton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,7 +561,75 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing
|
let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing
|
||||||
let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, additionalFields: additionalFields, publishableKey: publishableKey, completion: { method in
|
let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, provider: .stripe(additionalFields: additionalFields, publishableKey: publishableKey), completion: { method in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if canSave && paymentForm.passwordMissing {
|
||||||
|
switch method {
|
||||||
|
case let .webToken(webToken) where webToken.saveOnServer:
|
||||||
|
var text = strongSelf.presentationData.strings.Checkout_NewCard_SaveInfoEnableHelp
|
||||||
|
text = text.replacingOccurrences(of: "[", with: "")
|
||||||
|
text = text.replacingOccurrences(of: "]", with: "")
|
||||||
|
present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_NotNow, action: {
|
||||||
|
var updatedToken = webToken
|
||||||
|
updatedToken.saveOnServer = false
|
||||||
|
applyPaymentMethod(.webToken(updatedToken))
|
||||||
|
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if paymentForm.passwordMissing {
|
||||||
|
var updatedToken = webToken
|
||||||
|
updatedToken.saveOnServer = false
|
||||||
|
applyPaymentMethod(.webToken(updatedToken))
|
||||||
|
|
||||||
|
let controller = SetupTwoStepVerificationController(context: strongSelf.context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
|
||||||
|
if shouldDismiss {
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
switch update {
|
||||||
|
case .noPassword, .awaitingEmailConfirmation:
|
||||||
|
break
|
||||||
|
case .passwordSet:
|
||||||
|
var updatedToken = webToken
|
||||||
|
updatedToken.saveOnServer = true
|
||||||
|
applyPaymentMethod(.webToken(updatedToken))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
} else {
|
||||||
|
var updatedToken = webToken
|
||||||
|
updatedToken.saveOnServer = true
|
||||||
|
applyPaymentMethod(.webToken(updatedToken))
|
||||||
|
}
|
||||||
|
})]), nil)
|
||||||
|
default:
|
||||||
|
applyPaymentMethod(method)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
applyPaymentMethod(method)
|
||||||
|
}
|
||||||
|
dismissImpl?()
|
||||||
|
})
|
||||||
|
dismissImpl = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
} else if let nativeProvider = paymentForm.nativeProvider, nativeProvider.name == "smartglocal" {
|
||||||
|
guard let paramsData = nativeProvider.params.data(using: .utf8) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let nativeParams = (try? JSONSerialization.jsonObject(with: paramsData)) as? [String: Any] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let publicToken = nativeParams["public_token"] as? String else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var dismissImpl: (() -> Void)?
|
||||||
|
let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing
|
||||||
|
let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, provider: .smartglobal(isTesting: paymentForm.invoice.isTest, publicToken: publicToken), completion: { method in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -754,9 +832,12 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.addSubnode(self.actionButtonPanelNode)
|
||||||
|
self.actionButtonPanelNode.addSubnode(self.actionButtonPanelSeparator)
|
||||||
|
self.actionButtonPanelNode.addSubnode(self.actionButton)
|
||||||
|
|
||||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||||
self.actionButton.isEnabled = false
|
self.actionButton.isEnabled = false
|
||||||
self.addSubnode(self.actionButton)
|
|
||||||
|
|
||||||
self.listNode.supernode?.insertSubnode(self.inProgressDimNode, aboveSubnode: self.listNode)
|
self.listNode.supernode?.insertSubnode(self.inProgressDimNode, aboveSubnode: self.listNode)
|
||||||
}
|
}
|
||||||
@ -775,22 +856,37 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
} else {
|
} else {
|
||||||
payString = self.presentationData.strings.CheckoutInfo_Pay
|
payString = self.presentationData.strings.CheckoutInfo_Pay
|
||||||
}
|
}
|
||||||
if self.actionButton.isEnabled {
|
if let currentPaymentMethod = self.currentPaymentMethod {
|
||||||
|
switch currentPaymentMethod {
|
||||||
|
case .applePay:
|
||||||
|
self.actionButton.setState(.applePay)
|
||||||
|
default:
|
||||||
self.actionButton.setState(.active(payString))
|
self.actionButton.setState(.active(payString))
|
||||||
} else {
|
|
||||||
self.actionButton.setState(.loading)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.actionButton.setState(.active(payString))
|
||||||
|
}
|
||||||
|
self.actionButtonPanelNode.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
||||||
var updatedInsets = layout.intrinsicInsets
|
var updatedInsets = layout.intrinsicInsets
|
||||||
updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0
|
|
||||||
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets)
|
|
||||||
|
|
||||||
let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter))
|
let bottomPanelHorizontalInset: CGFloat = 16.0
|
||||||
|
let bottomPanelVerticalInset: CGFloat = 16.0
|
||||||
|
let bottomPanelHeight = updatedInsets.bottom + bottomPanelVerticalInset * 2.0 + BotCheckoutActionButton.height
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.actionButtonPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: layout.size.width, height: bottomPanelHeight)))
|
||||||
|
transition.updateFrame(node: self.actionButtonPanelSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
let actionButtonFrame = CGRect(origin: CGPoint(x: bottomPanelHorizontalInset, y: bottomPanelVerticalInset), size: CGSize(width: layout.size.width - bottomPanelHorizontalInset * 2.0, height: BotCheckoutActionButton.height))
|
||||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||||
self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition)
|
self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition)
|
||||||
|
|
||||||
|
updatedInsets.bottom = bottomPanelHeight
|
||||||
|
|
||||||
|
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets)
|
||||||
|
|
||||||
transition.updateFrame(node: self.inProgressDimNode, frame: self.listNode.frame)
|
transition.updateFrame(node: self.inProgressDimNode, frame: self.listNode.frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,6 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
|
|||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
self.backgroundNode.backgroundColor = .white
|
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
self.topStripeNode = ASDisplayNode()
|
||||||
self.topStripeNode.isLayerBacked = true
|
self.topStripeNode.isLayerBacked = true
|
||||||
@ -110,6 +109,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
@ -209,9 +209,9 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: contentInsets.left, y: contentInsets.top), size: imageSize)
|
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: contentInsets.left, y: contentInsets.top), size: imageSize)
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode != nil {
|
/*if strongSelf.backgroundNode.supernode != nil {
|
||||||
strongSelf.backgroundNode.removeFromSupernode()
|
strongSelf.backgroundNode.removeFromSupernode()
|
||||||
}
|
}*/
|
||||||
if strongSelf.topStripeNode.supernode != nil {
|
if strongSelf.topStripeNode.supernode != nil {
|
||||||
strongSelf.topStripeNode.removeFromSupernode()
|
strongSelf.topStripeNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -232,6 +232,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
strongSelf.botNameNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + textBotNameSpacing), size: botNameLayout.size)
|
strongSelf.botNameNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + textBotNameSpacing), size: botNameLayout.size)
|
||||||
|
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1000.0), size: CGSize(width: params.width, height: contentSize.height + 1000.0))
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -30,13 +30,17 @@ struct BotCheckoutNativeCardEntryAdditionalFields: OptionSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class BotCheckoutNativeCardEntryController: ViewController {
|
final class BotCheckoutNativeCardEntryController: ViewController {
|
||||||
|
enum Provider {
|
||||||
|
case stripe(additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String)
|
||||||
|
case smartglobal(isTesting: Bool, publicToken: String)
|
||||||
|
}
|
||||||
|
|
||||||
private var controllerNode: BotCheckoutNativeCardEntryControllerNode {
|
private var controllerNode: BotCheckoutNativeCardEntryControllerNode {
|
||||||
return super.displayNode as! BotCheckoutNativeCardEntryControllerNode
|
return super.displayNode as! BotCheckoutNativeCardEntryControllerNode
|
||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let additionalFields: BotCheckoutNativeCardEntryAdditionalFields
|
private let provider: Provider
|
||||||
private let publishableKey: String
|
|
||||||
private let completion: (BotCheckoutPaymentMethod) -> Void
|
private let completion: (BotCheckoutPaymentMethod) -> Void
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -46,10 +50,9 @@ final class BotCheckoutNativeCardEntryController: ViewController {
|
|||||||
private var doneItem: UIBarButtonItem?
|
private var doneItem: UIBarButtonItem?
|
||||||
private var activityItem: UIBarButtonItem?
|
private var activityItem: UIBarButtonItem?
|
||||||
|
|
||||||
public init(context: AccountContext, additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String, completion: @escaping (BotCheckoutPaymentMethod) -> Void) {
|
public init(context: AccountContext, provider: Provider, completion: @escaping (BotCheckoutPaymentMethod) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.additionalFields = additionalFields
|
self.provider = provider
|
||||||
self.publishableKey = publishableKey
|
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -71,7 +74,7 @@ final class BotCheckoutNativeCardEntryController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = BotCheckoutNativeCardEntryControllerNode(additionalFields: self.additionalFields, publishableKey: self.publishableKey, theme: self.presentationData.theme, strings: self.presentationData.strings, present: { [weak self] c, a in
|
self.displayNode = BotCheckoutNativeCardEntryControllerNode(provider: self.provider, theme: self.presentationData.theme, strings: self.presentationData.strings, present: { [weak self] c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}, dismiss: { [weak self] in
|
}, dismiss: { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
|||||||
@ -42,7 +42,7 @@ private final class BotCheckoutNativeCardEntryScrollerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||||
private let publishableKey: String
|
private let provider: BotCheckoutNativeCardEntryController.Provider
|
||||||
|
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
private let dismiss: () -> Void
|
private let dismiss: () -> Void
|
||||||
@ -71,8 +71,10 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
|
|||||||
private var currentCardData: BotPaymentCardInputData?
|
private var currentCardData: BotPaymentCardInputData?
|
||||||
private var currentCountryIso2: String?
|
private var currentCountryIso2: String?
|
||||||
|
|
||||||
init(additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String, theme: PresentationTheme, strings: PresentationStrings, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, openCountrySelection: @escaping () -> Void, updateStatus: @escaping (BotCheckoutNativeCardEntryStatus) -> Void, completion: @escaping (BotCheckoutPaymentMethod) -> Void) {
|
private var dataTask: URLSessionDataTask?
|
||||||
self.publishableKey = publishableKey
|
|
||||||
|
init(provider: BotCheckoutNativeCardEntryController.Provider, theme: PresentationTheme, strings: PresentationStrings, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, openCountrySelection: @escaping () -> Void, updateStatus: @escaping (BotCheckoutNativeCardEntryStatus) -> Void, completion: @escaping (BotCheckoutPaymentMethod) -> Void) {
|
||||||
|
self.provider = provider
|
||||||
|
|
||||||
self.present = present
|
self.present = present
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
@ -96,6 +98,8 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
|
|||||||
}
|
}
|
||||||
itemNodes.append([BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_PaymentCard), self.cardItem])
|
itemNodes.append([BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_PaymentCard), self.cardItem])
|
||||||
|
|
||||||
|
switch provider {
|
||||||
|
case let .stripe(additionalFields, _):
|
||||||
if additionalFields.contains(.cardholderName) {
|
if additionalFields.contains(.cardholderName) {
|
||||||
var sectionItems: [BotPaymentItemNode] = []
|
var sectionItems: [BotPaymentItemNode] = []
|
||||||
|
|
||||||
@ -138,6 +142,11 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
|
|||||||
self.countryItem = nil
|
self.countryItem = nil
|
||||||
self.zipCodeItem = nil
|
self.zipCodeItem = nil
|
||||||
}
|
}
|
||||||
|
case .smartglobal:
|
||||||
|
self.cardholderItem = nil
|
||||||
|
self.countryItem = nil
|
||||||
|
self.zipCodeItem = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.saveInfoItem = BotPaymentSwitchItemNode(title: strings.Checkout_NewCard_SaveInfo, isOn: false)
|
self.saveInfoItem = BotPaymentSwitchItemNode(title: strings.Checkout_NewCard_SaveInfo, isOn: false)
|
||||||
itemNodes.append([self.saveInfoItem, BotPaymentTextItemNode(text: strings.Checkout_NewCard_SaveInfoHelp)])
|
itemNodes.append([self.saveInfoItem, BotPaymentTextItemNode(text: strings.Checkout_NewCard_SaveInfoHelp)])
|
||||||
@ -214,6 +223,7 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.verifyDisposable.dispose()
|
self.verifyDisposable.dispose()
|
||||||
|
self.dataTask?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCountry(_ iso2: String) {
|
func updateCountry(_ iso2: String) {
|
||||||
@ -233,9 +243,11 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch self.provider {
|
||||||
|
case let .stripe(_, publishableKey):
|
||||||
let configuration = STPPaymentConfiguration.shared().copy() as! STPPaymentConfiguration
|
let configuration = STPPaymentConfiguration.shared().copy() as! STPPaymentConfiguration
|
||||||
configuration.smsAutofillDisabled = true
|
configuration.smsAutofillDisabled = true
|
||||||
configuration.publishableKey = self.publishableKey
|
configuration.publishableKey = publishableKey
|
||||||
configuration.appleMerchantIdentifier = "merchant.ph.telegra.Telegraph"
|
configuration.appleMerchantIdentifier = "merchant.ph.telegra.Telegraph"
|
||||||
|
|
||||||
let apiClient = STPAPIClient(configuration: configuration)
|
let apiClient = STPAPIClient(configuration: configuration)
|
||||||
@ -279,6 +291,100 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
self.updateDone()
|
self.updateDone()
|
||||||
|
case let .smartglobal(isTesting, publicToken):
|
||||||
|
let url: String
|
||||||
|
if isTesting {
|
||||||
|
url = "https://tgb-playground.smart-glocal.com/cds/v1/tokenize/card"
|
||||||
|
} else {
|
||||||
|
url = "https://tgb.smart-glocal.com/cds/v1/tokenize/card"
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonPayload: [String: Any] = [
|
||||||
|
"card": [
|
||||||
|
"number": cardData.number,
|
||||||
|
"expiration_month": "\(cardData.month)",
|
||||||
|
"expiration_year": "\(cardData.year)",
|
||||||
|
"security_code": "\(cardData.code)"
|
||||||
|
] as [String: Any]
|
||||||
|
]
|
||||||
|
|
||||||
|
guard let parsedUrl = URL(string: url) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = URLRequest(url: parsedUrl)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
request.setValue(publicToken, forHTTPHeaderField: "X-PUBLIC-TOKEN")
|
||||||
|
guard let requestBody = try? JSONSerialization.data(withJSONObject: jsonPayload, options: []) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
request.httpBody = requestBody
|
||||||
|
|
||||||
|
let session = URLSession.shared
|
||||||
|
let dataTask = session.dataTask(with: request, completionHandler: { [weak self] data, response, error in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ReponseError: Error {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
guard let data = data else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonRaw = try JSONSerialization.jsonObject(with: data, options: [])
|
||||||
|
guard let json = jsonRaw as? [String: Any] else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
guard let resultData = json["data"] as? [String: Any] else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
guard let resultInfo = resultData["info"] as? [String: Any] else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
guard let token = resultData["token"] as? String else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
guard let maskedCardNumber = resultInfo["masked_card_number"] as? String else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseJson: [String: Any] = [
|
||||||
|
"type": "card",
|
||||||
|
"id": "\(token)"
|
||||||
|
]
|
||||||
|
|
||||||
|
let serializedResponseJson = try JSONSerialization.data(withJSONObject: responseJson, options: [])
|
||||||
|
|
||||||
|
guard let serializedResponseString = String(data: serializedResponseJson, encoding: .utf8) else {
|
||||||
|
throw ReponseError.generic
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.completion(.webToken(BotCheckoutPaymentWebToken(
|
||||||
|
title: maskedCardNumber,
|
||||||
|
data: serializedResponseString,
|
||||||
|
saveOnServer: strongSelf.saveInfoItem.isOn
|
||||||
|
)))
|
||||||
|
} catch {
|
||||||
|
strongSelf.isVerifying = false
|
||||||
|
strongSelf.updateDone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.dataTask = dataTask
|
||||||
|
|
||||||
|
self.isVerifying = true
|
||||||
|
self.updateDone()
|
||||||
|
|
||||||
|
dataTask.resume()
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateDone() {
|
private func updateDone() {
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class BotCheckoutPriceItem: ListViewItem, ItemListItem {
|
|||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = BotCheckoutPriceItemNode()
|
let node = BotCheckoutPriceItemNode()
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), previousItem, nextItem)
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
node.contentSize = layout.contentSize
|
||||||
node.insets = layout.insets
|
node.insets = layout.insets
|
||||||
@ -48,7 +48,7 @@ class BotCheckoutPriceItem: ListViewItem, ItemListItem {
|
|||||||
let makeLayout = nodeValue.asyncLayout()
|
let makeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
async {
|
async {
|
||||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), previousItem, nextItem)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(layout, { _ in
|
completion(layout, { _ in
|
||||||
apply()
|
apply()
|
||||||
@ -69,13 +69,13 @@ private func priceItemInsets(_ neighbors: ItemListNeighbors) -> UIEdgeInsets {
|
|||||||
var insets = UIEdgeInsets()
|
var insets = UIEdgeInsets()
|
||||||
switch neighbors.top {
|
switch neighbors.top {
|
||||||
case .otherSection:
|
case .otherSection:
|
||||||
insets.top += 8.0
|
insets.top += 24.0
|
||||||
case .none, .sameSection:
|
case .none, .sameSection:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch neighbors.bottom {
|
switch neighbors.bottom {
|
||||||
case .none, .otherSection:
|
case .none, .otherSection:
|
||||||
insets.bottom += 8.0
|
insets.bottom += 24.0
|
||||||
case .sameSection:
|
case .sameSection:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -86,9 +86,9 @@ class BotCheckoutPriceItemNode: ListViewItemNode {
|
|||||||
let titleNode: TextNode
|
let titleNode: TextNode
|
||||||
let labelNode: TextNode
|
let labelNode: TextNode
|
||||||
|
|
||||||
|
let backgroundNode: ASDisplayNode
|
||||||
let separatorNode: ASDisplayNode
|
let separatorNode: ASDisplayNode
|
||||||
let bottomSeparatorNode: ASDisplayNode
|
let bottomSeparatorNode: ASDisplayNode
|
||||||
let spacerNode: ASDisplayNode
|
|
||||||
|
|
||||||
private var item: BotCheckoutPriceItem?
|
private var item: BotCheckoutPriceItem?
|
||||||
|
|
||||||
@ -99,37 +99,44 @@ class BotCheckoutPriceItemNode: ListViewItemNode {
|
|||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.isUserInteractionEnabled = false
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.bottomSeparatorNode = ASDisplayNode()
|
self.bottomSeparatorNode = ASDisplayNode()
|
||||||
self.spacerNode = ASDisplayNode()
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.spacerNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
self.addSubnode(self.separatorNode)
|
self.addSubnode(self.separatorNode)
|
||||||
self.addSubnode(self.bottomSeparatorNode)
|
self.addSubnode(self.bottomSeparatorNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: BotCheckoutPriceItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: BotCheckoutPriceItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors, _ previousItem: ListViewItem?, _ nextItem: ListViewItem?) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
|
||||||
return { item, params, neighbors in
|
return { item, params, neighbors, previousItem, nextItem in
|
||||||
let rightInset: CGFloat = 16.0 + params.rightInset
|
let rightInset: CGFloat = 16.0 + params.rightInset
|
||||||
|
|
||||||
let naturalContentHeight: CGFloat = 34.0
|
let naturalContentHeight: CGFloat
|
||||||
|
var verticalOffset: CGFloat = 0.0
|
||||||
var contentSize = CGSize(width: params.width, height: naturalContentHeight)
|
|
||||||
var insets = priceItemInsets(neighbors)
|
|
||||||
|
|
||||||
if item.hasSeparator {
|
|
||||||
insets.top += 5.0
|
|
||||||
}
|
|
||||||
if item.isFinal {
|
if item.isFinal {
|
||||||
contentSize.height += 34.0
|
naturalContentHeight = 44.0
|
||||||
|
} else {
|
||||||
|
naturalContentHeight = 34.0
|
||||||
}
|
}
|
||||||
|
if let _ = previousItem as? BotCheckoutHeaderItem {
|
||||||
|
verticalOffset += 8.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentSize = CGSize(width: params.width, height: naturalContentHeight + verticalOffset)
|
||||||
|
if let nextItem = nextItem as? BotCheckoutPriceItem {
|
||||||
|
if nextItem.isFinal {
|
||||||
|
contentSize.height += 8.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let insets = priceItemInsets(neighbors)
|
||||||
|
|
||||||
let textFont: UIFont
|
let textFont: UIFont
|
||||||
let textColor: UIColor
|
let textColor: UIColor
|
||||||
@ -154,21 +161,15 @@ class BotCheckoutPriceItemNode: ListViewItemNode {
|
|||||||
let leftInset: CGFloat = 16.0 + params.leftInset
|
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||||
|
|
||||||
strongSelf.separatorNode.isHidden = !item.hasSeparator
|
strongSelf.separatorNode.isHidden = !item.hasSeparator
|
||||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.separatorNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
|
||||||
|
|
||||||
strongSelf.bottomSeparatorNode.isHidden = !item.isFinal
|
strongSelf.bottomSeparatorNode.isHidden = !item.isFinal
|
||||||
strongSelf.bottomSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.bottomSeparatorNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
strongSelf.bottomSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: naturalContentHeight + 10.0), size: CGSize(width: params.width, height: UIScreenPixel))
|
strongSelf.bottomSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: CGSize(width: params.width, height: UIScreenPixel))
|
||||||
|
|
||||||
strongSelf.spacerNode.isHidden = !item.isFinal
|
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||||
strongSelf.spacerNode.backgroundColor = item.theme.list.blocksBackgroundColor
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height))
|
||||||
strongSelf.spacerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: naturalContentHeight + 10.0 + UIScreenPixel), size: CGSize(width: params.width, height: max(0.0, contentSize.height - naturalContentHeight - UIScreenPixel)))
|
|
||||||
|
|
||||||
var verticalOffset: CGFloat = 0.0
|
|
||||||
if item.hasSeparator {
|
|
||||||
verticalOffset += 5.0
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalOffset + floor((naturalContentHeight - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalOffset + floor((naturalContentHeight - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: verticalOffset + floor((naturalContentHeight - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: verticalOffset + floor((naturalContentHeight - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||||
|
|||||||
@ -122,7 +122,7 @@ private final class TipValueNode: ASDisplayNode {
|
|||||||
|
|
||||||
func update(theme: PresentationTheme, text: String, isHighlighted: Bool, height: CGFloat) -> (CGFloat, (CGFloat) -> Void) {
|
func update(theme: PresentationTheme, text: String, isHighlighted: Bool, height: CGFloat) -> (CGFloat, (CGFloat) -> Void) {
|
||||||
var updateBackground = false
|
var updateBackground = false
|
||||||
let backgroundColor = isHighlighted ? UIColor(rgb: 0x00A650) : UIColor(rgb: 0xE5F6ED)
|
let backgroundColor = isHighlighted ? theme.list.paymentOption.activeFillColor : theme.list.paymentOption.inactiveFillColor
|
||||||
if let currentBackgroundColor = self.currentBackgroundColor {
|
if let currentBackgroundColor = self.currentBackgroundColor {
|
||||||
if !currentBackgroundColor.isEqual(backgroundColor) {
|
if !currentBackgroundColor.isEqual(backgroundColor) {
|
||||||
updateBackground = true
|
updateBackground = true
|
||||||
@ -135,7 +135,7 @@ private final class TipValueNode: ASDisplayNode {
|
|||||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: backgroundColor)
|
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.semibold(15.0), textColor: isHighlighted ? UIColor(rgb: 0xffffff) : UIColor(rgb: 0x00A650))
|
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.semibold(15.0), textColor: isHighlighted ? theme.list.paymentOption.activeForegroundColor : theme.list.paymentOption.inactiveForegroundColor)
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: height))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: height))
|
||||||
|
|
||||||
let minWidth: CGFloat = 80.0
|
let minWidth: CGFloat = 80.0
|
||||||
@ -154,20 +154,23 @@ private final class TipValueNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
let titleNode: TextNode
|
let titleNode: TextNode
|
||||||
let labelNode: TextNode
|
let labelNode: TextNode
|
||||||
let tipMeasurementNode: ImmediateTextNode
|
let tipMeasurementNode: ImmediateTextNode
|
||||||
let tipCurrencyNode: ImmediateTextNode
|
let tipCurrencyNode: ImmediateTextNode
|
||||||
private let textNode: TextFieldNode
|
private let textNode: TextFieldNode
|
||||||
|
|
||||||
private var formatterDelegate: CurrencyUITextFieldDelegate?
|
|
||||||
|
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
private var valueNodes: [TipValueNode] = []
|
private var valueNodes: [TipValueNode] = []
|
||||||
|
|
||||||
private var item: BotCheckoutTipItem?
|
private var item: BotCheckoutTipItem?
|
||||||
|
|
||||||
|
private var formatterDelegate: CurrencyUITextFieldDelegate?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
@ -192,6 +195,8 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
@ -272,7 +277,11 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
var textInputFrame = CGRect(origin: CGPoint(x: params.width - leftInset - 150.0, y: -2.0), size: CGSize(width: 150.0, height: labelsContentHeight))
|
var textInputFrame = CGRect(origin: CGPoint(x: params.width - leftInset - 150.0, y: -2.0), size: CGSize(width: 150.0, height: labelsContentHeight))
|
||||||
|
|
||||||
var currencyText: (String, String) = formatCurrencyAmountCustom(item.numericValue, currency: item.currency)
|
let currencyText: (String, String, Bool) = formatCurrencyAmountCustom(item.numericValue, currency: item.currency)
|
||||||
|
|
||||||
|
let currencySymbolOnTheLeft = currencyText.2
|
||||||
|
//let currencySymbolOnTheLeft = true
|
||||||
|
|
||||||
if strongSelf.textNode.textField.text ?? "" != currencyText.0 {
|
if strongSelf.textNode.textField.text ?? "" != currencyText.0 {
|
||||||
strongSelf.textNode.textField.text = currencyText.0
|
strongSelf.textNode.textField.text = currencyText.0
|
||||||
strongSelf.labelNode.isHidden = !currencyText.0.isEmpty
|
strongSelf.labelNode.isHidden = !currencyText.0.isEmpty
|
||||||
@ -281,10 +290,16 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
strongSelf.tipMeasurementNode.attributedText = NSAttributedString(string: currencyText.0, font: titleFont, textColor: textColor)
|
strongSelf.tipMeasurementNode.attributedText = NSAttributedString(string: currencyText.0, font: titleFont, textColor: textColor)
|
||||||
let inputTextSize = strongSelf.tipMeasurementNode.updateLayout(textInputFrame.size)
|
let inputTextSize = strongSelf.tipMeasurementNode.updateLayout(textInputFrame.size)
|
||||||
|
|
||||||
|
let spaceRect = NSAttributedString(string: " ", font: titleFont, textColor: textColor).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
|
||||||
strongSelf.tipCurrencyNode.attributedText = NSAttributedString(string: "\(currencyText.1)", font: titleFont, textColor: textColor)
|
strongSelf.tipCurrencyNode.attributedText = NSAttributedString(string: "\(currencyText.1)", font: titleFont, textColor: textColor)
|
||||||
let currencySize = strongSelf.tipCurrencyNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude))
|
let currencySize = strongSelf.tipCurrencyNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude))
|
||||||
|
if currencySymbolOnTheLeft {
|
||||||
|
strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width - inputTextSize.width - spaceRect.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize)
|
||||||
|
} else {
|
||||||
strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize)
|
strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize)
|
||||||
textInputFrame.origin.x -= currencySize.width
|
textInputFrame.origin.x -= currencySize.width + spaceRect.width
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.textNode.frame = textInputFrame
|
strongSelf.textNode.frame = textInputFrame
|
||||||
|
|
||||||
@ -347,6 +362,9 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: valueY), size: CGSize(width: params.width, height: max(0.0, contentSize.height - valueY)))
|
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: valueY), size: CGSize(width: params.width, height: max(0.0, contentSize.height - valueY)))
|
||||||
strongSelf.scrollNode.view.contentSize = CGSize(width: variantsOffset, height: strongSelf.scrollNode.frame.height)
|
strongSelf.scrollNode.view.contentSize = CGSize(width: variantsOffset, height: strongSelf.scrollNode.frame.height)
|
||||||
|
|
||||||
|
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -382,7 +400,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
if value > item.maxValue {
|
if value > item.maxValue {
|
||||||
value = item.maxValue
|
value = item.maxValue
|
||||||
|
|
||||||
let currencyText: (String, String) = formatCurrencyAmountCustom(value, currency: item.currency)
|
let currencyText = formatCurrencyAmountCustom(value, currency: item.currency)
|
||||||
if self.textNode.textField.text ?? "" != currencyText.0 {
|
if self.textNode.textField.text ?? "" != currencyText.0 {
|
||||||
self.textNode.textField.text = currencyText.0
|
self.textNode.textField.text = currencyText.0
|
||||||
}
|
}
|
||||||
@ -400,6 +418,11 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
|
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: textField.endOfDocument, to: textField.endOfDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: textField.endOfDocument, to: textField.endOfDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
|
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
|
|||||||
@ -117,7 +117,7 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
textInset = max(measuredInset, textInset)
|
textInset = max(measuredInset, textInset)
|
||||||
|
|
||||||
transition.updateFrame(node: self.textField, frame: CGRect(origin: CGPoint(x: textInset, y: 3.0), size: CGSize(width: max(1.0, width - textInset - 8.0), height: 40.0)))
|
transition.updateFrame(node: self.textField, frame: CGRect(origin: CGPoint(x: textInset, y: 0.0), size: CGSize(width: max(1.0, width - textInset - 8.0), height: 40.0)))
|
||||||
|
|
||||||
return 44.0
|
return 44.0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,16 +20,14 @@ public final class BotReceiptController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let invoice: TelegramMediaInvoice
|
|
||||||
private let messageId: MessageId
|
private let messageId: MessageId
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
private var didPlayPresentationAnimation = false
|
private var didPlayPresentationAnimation = false
|
||||||
|
|
||||||
public init(context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId) {
|
public init(context: AccountContext, messageId: MessageId) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.invoice = invoice
|
|
||||||
self.messageId = messageId
|
self.messageId = messageId
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -38,10 +36,10 @@ public final class BotReceiptController: ViewController {
|
|||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||||
|
|
||||||
var title = self.presentationData.strings.Checkout_Receipt_Title
|
let title = self.presentationData.strings.Checkout_Receipt_Title
|
||||||
if invoice.flags.contains(.isTest) {
|
/*if invoice.flags.contains(.isTest) {
|
||||||
title += " (Test)"
|
title += " (Test)"
|
||||||
}
|
}*/
|
||||||
self.title = title
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +52,7 @@ public final class BotReceiptController: ViewController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.navigationOffset = offset
|
strongSelf.navigationOffset = offset
|
||||||
}
|
}
|
||||||
}, context: self.context, invoice: self.invoice, messageId: self.messageId, dismissAnimated: { [weak self] in
|
}, context: self.context, messageId: self.messageId, dismissAnimated: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ private enum BotReceiptSection: Int32 {
|
|||||||
|
|
||||||
enum BotReceiptEntry: ItemListNodeEntry {
|
enum BotReceiptEntry: ItemListNodeEntry {
|
||||||
case header(PresentationTheme, TelegramMediaInvoice, String)
|
case header(PresentationTheme, TelegramMediaInvoice, String)
|
||||||
case price(Int, PresentationTheme, String, String, Bool)
|
case price(Int, PresentationTheme, String, String, Bool, Bool)
|
||||||
case paymentMethod(PresentationTheme, String, String)
|
case paymentMethod(PresentationTheme, String, String)
|
||||||
case shippingInfo(PresentationTheme, String, String)
|
case shippingInfo(PresentationTheme, String, String)
|
||||||
case shippingMethod(PresentationTheme, String, String)
|
case shippingMethod(PresentationTheme, String, String)
|
||||||
@ -39,7 +39,7 @@ enum BotReceiptEntry: ItemListNodeEntry {
|
|||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .header:
|
case .header:
|
||||||
return BotReceiptSection.header.rawValue
|
return BotReceiptSection.prices.rawValue
|
||||||
case .price:
|
case .price:
|
||||||
return BotReceiptSection.prices.rawValue
|
return BotReceiptSection.prices.rawValue
|
||||||
default:
|
default:
|
||||||
@ -51,7 +51,7 @@ enum BotReceiptEntry: ItemListNodeEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case .header:
|
case .header:
|
||||||
return 0
|
return 0
|
||||||
case let .price(index, _, _, _, _):
|
case let .price(index, _, _, _, _, _):
|
||||||
return 1 + Int32(index)
|
return 1 + Int32(index)
|
||||||
case .paymentMethod:
|
case .paymentMethod:
|
||||||
return 10000 + 0
|
return 10000 + 0
|
||||||
@ -85,8 +85,8 @@ enum BotReceiptEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .price(lhsIndex, lhsTheme, lhsText, lhsValue, lhsFinal):
|
case let .price(lhsIndex, lhsTheme, lhsText, lhsValue, lhsHasSeparator, lhsFinal):
|
||||||
if case let .price(rhsIndex, rhsTheme, rhsText, rhsValue, rhsFinal) = rhs {
|
if case let .price(rhsIndex, rhsTheme, rhsText, rhsValue, rhsHasSeparator, rhsFinal) = rhs {
|
||||||
if lhsIndex != rhsIndex {
|
if lhsIndex != rhsIndex {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -99,6 +99,9 @@ enum BotReceiptEntry: ItemListNodeEntry {
|
|||||||
if lhsValue != rhsValue {
|
if lhsValue != rhsValue {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhsHasSeparator != rhsHasSeparator {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhsFinal != rhsFinal {
|
if lhsFinal != rhsFinal {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -154,8 +157,8 @@ enum BotReceiptEntry: ItemListNodeEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .header(theme, invoice, botName):
|
case let .header(theme, invoice, botName):
|
||||||
return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section)
|
return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section)
|
||||||
case let .price(_, theme, text, value, isFinal):
|
case let .price(_, theme, text, value, hasSeparator, isFinal):
|
||||||
return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: false, sectionId: self.section)
|
return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: hasSeparator, sectionId: self.section)
|
||||||
case let .paymentMethod(_, text, value):
|
case let .paymentMethod(_, text, value):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: nil)
|
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: nil)
|
||||||
case let .shippingInfo(_, text, value):
|
case let .shippingInfo(_, text, value):
|
||||||
@ -172,21 +175,23 @@ enum BotReceiptEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func botReceiptControllerEntries(presentationData: PresentationData, invoice: TelegramMediaInvoice, formInvoice: BotPaymentInvoice?, formInfo: BotPaymentRequestedInfo?, shippingOption: BotPaymentShippingOption?, paymentMethodTitle: String?, botPeer: Peer?) -> [BotReceiptEntry] {
|
private func botReceiptControllerEntries(presentationData: PresentationData, invoice: TelegramMediaInvoice?, formInvoice: BotPaymentInvoice?, formInfo: BotPaymentRequestedInfo?, shippingOption: BotPaymentShippingOption?, paymentMethodTitle: String?, botPeer: Peer?, tipAmount: Int64?) -> [BotReceiptEntry] {
|
||||||
var entries: [BotReceiptEntry] = []
|
var entries: [BotReceiptEntry] = []
|
||||||
|
|
||||||
var botName = ""
|
var botName = ""
|
||||||
if let botPeer = botPeer {
|
if let botPeer = botPeer {
|
||||||
botName = botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
botName = botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
}
|
}
|
||||||
|
if let invoice = invoice {
|
||||||
entries.append(.header(presentationData.theme, invoice, botName))
|
entries.append(.header(presentationData.theme, invoice, botName))
|
||||||
|
}
|
||||||
|
|
||||||
if let formInvoice = formInvoice {
|
if let formInvoice = formInvoice {
|
||||||
var totalPrice: Int64 = 0
|
var totalPrice: Int64 = 0
|
||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
for price in formInvoice.prices {
|
for price in formInvoice.prices {
|
||||||
entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), false))
|
entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), index == 0, false))
|
||||||
totalPrice += price.amount
|
totalPrice += price.amount
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -196,13 +201,20 @@ private func botReceiptControllerEntries(presentationData: PresentationData, inv
|
|||||||
shippingOptionString = shippingOption.title
|
shippingOptionString = shippingOption.title
|
||||||
|
|
||||||
for price in shippingOption.prices {
|
for price in shippingOption.prices {
|
||||||
entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), false))
|
entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), index == 0, false))
|
||||||
totalPrice += price.amount
|
totalPrice += price.amount
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.price(index, presentationData.theme, presentationData.strings.Checkout_TotalAmount, formatCurrencyAmount(totalPrice, currency: formInvoice.currency), true))
|
if let tipAmount = tipAmount, tipAmount != 0 {
|
||||||
|
//TODO:localize
|
||||||
|
entries.append(.price(index, presentationData.theme, "Tip", formatCurrencyAmount(tipAmount, currency: formInvoice.currency), index == 0, false))
|
||||||
|
totalPrice += tipAmount
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.price(index, presentationData.theme, presentationData.strings.Checkout_TotalAmount, formatCurrencyAmount(totalPrice, currency: formInvoice.currency), true, true))
|
||||||
|
|
||||||
if let paymentMethodTitle = paymentMethodTitle {
|
if let paymentMethodTitle = paymentMethodTitle {
|
||||||
entries.append(.paymentMethod(presentationData.theme, presentationData.strings.Checkout_PaymentMethod, paymentMethodTitle))
|
entries.append(.paymentMethod(presentationData.theme, presentationData.strings.Checkout_PaymentMethod, paymentMethodTitle))
|
||||||
@ -262,12 +274,12 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
|||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
private let receiptData = Promise<(BotPaymentInvoice, BotPaymentRequestedInfo?, BotPaymentShippingOption?, String?)?>(nil)
|
private let receiptData = Promise<(BotPaymentInvoice, BotPaymentRequestedInfo?, BotPaymentShippingOption?, String?, TelegramMediaInvoice, Int64?)?>(nil)
|
||||||
private var dataRequestDisposable: Disposable?
|
private var dataRequestDisposable: Disposable?
|
||||||
|
|
||||||
private let actionButton: BotCheckoutActionButton
|
private let actionButton: BotCheckoutActionButton
|
||||||
|
|
||||||
init(controller: ItemListController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, dismissAnimated: @escaping () -> Void) {
|
init(controller: ItemListController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, messageId: MessageId, dismissAnimated: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.dismissAnimated = dismissAnimated
|
self.dismissAnimated = dismissAnimated
|
||||||
|
|
||||||
@ -277,19 +289,19 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
|||||||
|
|
||||||
let signal: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError> = combineLatest(context.sharedContext.presentationData, receiptData.get(), context.account.postbox.loadedPeerWithId(messageId.peerId))
|
let signal: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError> = combineLatest(context.sharedContext.presentationData, receiptData.get(), context.account.postbox.loadedPeerWithId(messageId.peerId))
|
||||||
|> map { presentationData, receiptData, botPeer -> (ItemListPresentationData, (ItemListNodeState, Any)) in
|
|> map { presentationData, receiptData, botPeer -> (ItemListPresentationData, (ItemListNodeState, Any)) in
|
||||||
let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botReceiptControllerEntries(presentationData: presentationData, invoice: invoice, formInvoice: receiptData?.0, formInfo: receiptData?.1, shippingOption: receiptData?.2, paymentMethodTitle: receiptData?.3, botPeer: botPeer), style: .plain, focusItemTag: nil, emptyStateItem: nil, animateChanges: false)
|
let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botReceiptControllerEntries(presentationData: presentationData, invoice: receiptData?.4, formInvoice: receiptData?.0, formInfo: receiptData?.1, shippingOption: receiptData?.2, paymentMethodTitle: receiptData?.3, botPeer: botPeer, tipAmount: receiptData?.5), style: .blocks, focusItemTag: nil, emptyStateItem: nil, animateChanges: false)
|
||||||
|
|
||||||
return (ItemListPresentationData(presentationData), (nodeState, arguments))
|
return (ItemListPresentationData(presentationData), (nodeState, arguments))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.plainBackgroundColor)
|
self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.plainBackgroundColor)
|
||||||
self.actionButton.setState(.inactive(self.presentationData.strings.Common_Done))
|
self.actionButton.setState(.active(self.presentationData.strings.Common_Done))
|
||||||
|
|
||||||
super.init(controller: controller, navigationBar: navigationBar, updateNavigationOffset: updateNavigationOffset, state: signal)
|
super.init(controller: controller, navigationBar: navigationBar, updateNavigationOffset: updateNavigationOffset, state: signal)
|
||||||
|
|
||||||
self.dataRequestDisposable = (requestBotPaymentReceipt(account: context.account, messageId: messageId) |> deliverOnMainQueue).start(next: { [weak self] receipt in
|
self.dataRequestDisposable = (requestBotPaymentReceipt(account: context.account, messageId: messageId) |> deliverOnMainQueue).start(next: { [weak self] receipt in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.receiptData.set(.single((receipt.invoice, receipt.info, receipt.shippingOption, receipt.credentialsTitle)))
|
strongSelf.receiptData.set(.single((receipt.invoice, receipt.info, receipt.shippingOption, receipt.credentialsTitle, receipt.invoiceMedia, receipt.tipAmount)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -303,10 +315,11 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
|||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
||||||
var updatedInsets = layout.intrinsicInsets
|
var updatedInsets = layout.intrinsicInsets
|
||||||
updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0
|
updatedInsets.bottom += BotCheckoutActionButton.height + 16.0 * 2.0
|
||||||
|
|
||||||
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets)
|
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets)
|
||||||
|
|
||||||
let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter))
|
let actionButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: layout.size.height - 16.0 - BotCheckoutActionButton.height - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 16.0 * 2.0, height: BotCheckoutActionButton.height))
|
||||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||||
self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition)
|
self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,6 +108,12 @@ extension CurrencyUITextFieldDelegate: UITextFieldDelegate {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||||
|
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
|
||||||
|
passthroughDelegate?.textFieldDidChangeSelection?(textField)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|||||||
@ -30,15 +30,18 @@ final class PinchSourceGesture: UIPinchGestureRecognizer {
|
|||||||
|
|
||||||
private let target: Target
|
private let target: Target
|
||||||
|
|
||||||
private(set) var currentTransform: (CGFloat, CGPoint)?
|
private(set) var currentTransform: (CGFloat, CGPoint, CGPoint)?
|
||||||
|
|
||||||
var began: (() -> Void)?
|
var began: (() -> Void)?
|
||||||
var updated: ((CGFloat, CGPoint) -> Void)?
|
var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
|
||||||
var ended: (() -> Void)?
|
var ended: (() -> Void)?
|
||||||
|
|
||||||
private var lastLocation: CGPoint?
|
private var initialLocation: CGPoint?
|
||||||
|
private var pinchLocation = CGPoint()
|
||||||
private var currentOffset = CGPoint()
|
private var currentOffset = CGPoint()
|
||||||
|
|
||||||
|
private var currentNumberOfTouches = 0
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.target = Target()
|
self.target = Target()
|
||||||
|
|
||||||
@ -52,11 +55,14 @@ final class PinchSourceGesture: UIPinchGestureRecognizer {
|
|||||||
override func reset() {
|
override func reset() {
|
||||||
super.reset()
|
super.reset()
|
||||||
|
|
||||||
self.lastLocation = nil
|
self.currentNumberOfTouches = 0
|
||||||
|
self.initialLocation = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
super.touchesBegan(touches, with: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
|
//self.currentTouches.formUnion(touches)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
@ -69,40 +75,41 @@ final class PinchSourceGesture: UIPinchGestureRecognizer {
|
|||||||
|
|
||||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
super.touchesMoved(touches, with: event)
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
if touches.count >= 2 {
|
|
||||||
var locationSum = CGPoint()
|
|
||||||
for touch in touches {
|
|
||||||
let point = touch.location(in: self.view)
|
|
||||||
locationSum.x += point.x
|
|
||||||
locationSum.y += point.y
|
|
||||||
}
|
|
||||||
locationSum.x /= CGFloat(touches.count)
|
|
||||||
locationSum.y /= CGFloat(touches.count)
|
|
||||||
if let lastLocation = self.lastLocation {
|
|
||||||
self.currentOffset = CGPoint(x: locationSum.x - lastLocation.x, y: locationSum.y - lastLocation.y)
|
|
||||||
} else {
|
|
||||||
self.lastLocation = locationSum
|
|
||||||
self.currentOffset = CGPoint()
|
|
||||||
}
|
|
||||||
if let (scale, _) = self.currentTransform {
|
|
||||||
self.currentTransform = (scale, self.currentOffset)
|
|
||||||
self.updated?(scale, self.currentOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func gestureUpdated() {
|
private func gestureUpdated() {
|
||||||
switch self.state {
|
switch self.state {
|
||||||
case .began:
|
case .began:
|
||||||
self.lastLocation = nil
|
|
||||||
self.currentOffset = CGPoint()
|
self.currentOffset = CGPoint()
|
||||||
self.currentTransform = nil
|
|
||||||
|
let pinchLocation = self.location(in: self.view)
|
||||||
|
self.pinchLocation = pinchLocation
|
||||||
|
self.initialLocation = pinchLocation
|
||||||
|
let scale = max(1.0, self.scale)
|
||||||
|
self.currentTransform = (scale, self.pinchLocation, self.currentOffset)
|
||||||
|
|
||||||
|
self.currentNumberOfTouches = self.numberOfTouches
|
||||||
|
|
||||||
self.began?()
|
self.began?()
|
||||||
case .changed:
|
case .changed:
|
||||||
|
let locationSum = self.location(in: self.view)
|
||||||
|
|
||||||
|
if self.numberOfTouches < 2 && self.currentNumberOfTouches >= 2 {
|
||||||
|
self.initialLocation = CGPoint(x: locationSum.x - self.currentOffset.x, y: locationSum.y - self.currentOffset.y)
|
||||||
|
}
|
||||||
|
self.currentNumberOfTouches = self.numberOfTouches
|
||||||
|
|
||||||
|
if let initialLocation = self.initialLocation {
|
||||||
|
self.currentOffset = CGPoint(x: locationSum.x - initialLocation.x, y: locationSum.y - initialLocation.y)
|
||||||
|
}
|
||||||
|
if let (scale, pinchLocation, _) = self.currentTransform {
|
||||||
|
self.currentTransform = (scale, pinchLocation, self.currentOffset)
|
||||||
|
self.updated?(scale, pinchLocation, self.currentOffset)
|
||||||
|
}
|
||||||
|
|
||||||
let scale = max(1.0, self.scale)
|
let scale = max(1.0, self.scale)
|
||||||
self.currentTransform = (scale, self.currentOffset)
|
self.currentTransform = (scale, self.pinchLocation, self.currentOffset)
|
||||||
self.updated?(scale, self.currentOffset)
|
self.updated?(scale, self.pinchLocation, self.currentOffset)
|
||||||
case .ended, .cancelled:
|
case .ended, .cancelled:
|
||||||
self.ended?()
|
self.ended?()
|
||||||
default:
|
default:
|
||||||
@ -152,12 +159,14 @@ public final class PinchSourceContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var maxPinchScale: CGFloat = 10.0
|
||||||
|
|
||||||
private var isActive: Bool = false
|
private var isActive: Bool = false
|
||||||
|
|
||||||
public var activate: ((PinchSourceContainerNode) -> Void)?
|
public var activate: ((PinchSourceContainerNode) -> Void)?
|
||||||
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||||
var deactivate: (() -> Void)?
|
var deactivate: (() -> Void)?
|
||||||
var updated: ((CGFloat, CGPoint) -> Void)?
|
var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
self.gesture = PinchSourceGesture()
|
self.gesture = PinchSourceGesture()
|
||||||
@ -187,12 +196,12 @@ public final class PinchSourceContainerNode: ASDisplayNode {
|
|||||||
strongSelf.deactivate?()
|
strongSelf.deactivate?()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gesture.updated = { [weak self] scale, offset in
|
self.gesture.updated = { [weak self] scale, pinchLocation, offset in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.updated?(scale, offset)
|
strongSelf.updated?(min(scale, strongSelf.maxPinchScale), pinchLocation, offset)
|
||||||
strongSelf.scaleUpdated?(scale, .immediate)
|
strongSelf.scaleUpdated?(min(scale, strongSelf.maxPinchScale), .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +235,14 @@ public final class PinchSourceContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
private final class PinchControllerNode: ViewControllerTracingNode {
|
private final class PinchControllerNode: ViewControllerTracingNode {
|
||||||
private weak var controller: PinchController?
|
private weak var controller: PinchController?
|
||||||
|
|
||||||
|
private var initialSourceFrame: CGRect?
|
||||||
|
|
||||||
|
private let clippingNode: ASDisplayNode
|
||||||
|
private let scrollingContainer: ASDisplayNode
|
||||||
|
|
||||||
private let sourceNode: PinchSourceContainerNode
|
private let sourceNode: PinchSourceContainerNode
|
||||||
|
private let getContentAreaInScreenSpace: () -> CGRect
|
||||||
|
|
||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
|
|
||||||
@ -235,17 +251,25 @@ private final class PinchControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var hapticFeedback: HapticFeedback?
|
private var hapticFeedback: HapticFeedback?
|
||||||
|
|
||||||
init(controller: PinchController, sourceNode: PinchSourceContainerNode) {
|
init(controller: PinchController, sourceNode: PinchSourceContainerNode, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sourceNode = sourceNode
|
self.sourceNode = sourceNode
|
||||||
|
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
self.dimNode = ASDisplayNode()
|
||||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
self.dimNode.alpha = 0.0
|
self.dimNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.clippingNode = ASDisplayNode()
|
||||||
|
self.clippingNode.clipsToBounds = true
|
||||||
|
|
||||||
|
self.scrollingContainer = ASDisplayNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.dimNode)
|
self.addSubnode(self.dimNode)
|
||||||
|
self.addSubnode(self.clippingNode)
|
||||||
|
self.clippingNode.addSubnode(self.scrollingContainer)
|
||||||
|
|
||||||
self.sourceNode.deactivate = { [weak self] in
|
self.sourceNode.deactivate = { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -254,12 +278,22 @@ private final class PinchControllerNode: ViewControllerTracingNode {
|
|||||||
strongSelf.controller?.dismiss()
|
strongSelf.controller?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sourceNode.updated = { [weak self] scale, offset in
|
self.sourceNode.updated = { [weak self] scale, pinchLocation, offset in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let initialSourceFrame = strongSelf.initialSourceFrame else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.dimNode.alpha = max(0.0, min(1.0, scale - 1.0))
|
strongSelf.dimNode.alpha = max(0.0, min(1.0, scale - 1.0))
|
||||||
strongSelf.sourceNode.contentNode.transform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1.0), offset.x / scale, offset.y / scale, 0.0)
|
|
||||||
|
let pinchOffset = CGPoint(
|
||||||
|
x: pinchLocation.x - initialSourceFrame.width / 2.0,
|
||||||
|
y: pinchLocation.y - initialSourceFrame.height / 2.0
|
||||||
|
)
|
||||||
|
|
||||||
|
var transform = CATransform3DIdentity
|
||||||
|
transform = CATransform3DScale(transform, scale, scale, 0.0)
|
||||||
|
|
||||||
|
strongSelf.sourceNode.contentNode.transform = transform
|
||||||
|
strongSelf.sourceNode.contentNode.position = CGPoint(x: initialSourceFrame.midX + offset.x - pinchOffset.x * (scale - 1.0), y: initialSourceFrame.midY + offset.y - pinchOffset.y * (scale - 1.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,54 +312,95 @@ private final class PinchControllerNode: ViewControllerTracingNode {
|
|||||||
self.validLayout = layout
|
self.validLayout = layout
|
||||||
|
|
||||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
let convertedFrame = convertFrame(self.sourceNode.contentNode.frame, from: self.sourceNode.view, to: self.view)
|
let convertedFrame = convertFrame(self.sourceNode.bounds, from: self.sourceNode.view, to: self.view)
|
||||||
self.sourceNode.contentNode.frame = convertedFrame
|
self.sourceNode.contentNode.frame = convertedFrame
|
||||||
self.addSubnode(self.sourceNode.contentNode)
|
self.initialSourceFrame = convertedFrame
|
||||||
|
self.scrollingContainer.addSubnode(self.sourceNode.contentNode)
|
||||||
|
|
||||||
|
var updatedContentAreaInScreenSpace = self.getContentAreaInScreenSpace()
|
||||||
|
updatedContentAreaInScreenSpace.origin.x = 0.0
|
||||||
|
updatedContentAreaInScreenSpace.size.width = self.bounds.width
|
||||||
|
|
||||||
|
self.clippingNode.layer.animateFrame(from: updatedContentAreaInScreenSpace, to: self.clippingNode.frame, duration: 0.18 * 1.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||||
|
self.clippingNode.layer.animateBoundsOriginYAdditive(from: updatedContentAreaInScreenSpace.minY, to: 0.0, duration: 0.18 * 1.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
self.isAnimatingOut = true
|
||||||
|
|
||||||
let performCompletion: () -> Void = { [weak self] in
|
let performCompletion: () -> Void = { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.isAnimatingOut = false
|
||||||
|
|
||||||
strongSelf.sourceNode.restoreToNaturalSize()
|
strongSelf.sourceNode.restoreToNaturalSize()
|
||||||
strongSelf.sourceNode.addSubnode(strongSelf.sourceNode.contentNode)
|
strongSelf.sourceNode.addSubnode(strongSelf.sourceNode.contentNode)
|
||||||
|
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (scale, offset) = self.sourceNode.gesture.currentTransform {
|
let convertedFrame = convertFrame(self.sourceNode.bounds, from: self.sourceNode.view, to: self.view)
|
||||||
let duration = 0.4
|
self.sourceNode.contentNode.frame = convertedFrame
|
||||||
|
self.initialSourceFrame = convertedFrame
|
||||||
|
|
||||||
|
if let (scale, pinchLocation, offset) = self.sourceNode.gesture.currentTransform, let initialSourceFrame = self.initialSourceFrame {
|
||||||
|
let duration = 0.3
|
||||||
|
let transitionCurve: ContainedViewLayoutTransitionCurve = .easeInOut
|
||||||
|
|
||||||
|
var updatedContentAreaInScreenSpace = self.getContentAreaInScreenSpace()
|
||||||
|
updatedContentAreaInScreenSpace.origin.x = 0.0
|
||||||
|
updatedContentAreaInScreenSpace.size.width = self.bounds.width
|
||||||
|
|
||||||
|
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: duration * 1.0, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||||
|
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: duration * 1.0, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .spring)
|
||||||
if self.hapticFeedback == nil {
|
if self.hapticFeedback == nil {
|
||||||
self.hapticFeedback = HapticFeedback()
|
self.hapticFeedback = HapticFeedback()
|
||||||
}
|
}
|
||||||
self.hapticFeedback?.prepareImpact(.light)
|
self.hapticFeedback?.prepareImpact(.light)
|
||||||
Queue.mainQueue().after(0.2, { [weak self] in
|
self.hapticFeedback?.impact(.light)
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.hapticFeedback?.impact(.light)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.sourceNode.scaleUpdated?(1.0, transition)
|
self.sourceNode.scaleUpdated?(1.0, transition)
|
||||||
|
|
||||||
|
let pinchOffset = CGPoint(
|
||||||
|
x: pinchLocation.x - initialSourceFrame.width / 2.0,
|
||||||
|
y: pinchLocation.y - initialSourceFrame.height / 2.0
|
||||||
|
)
|
||||||
|
|
||||||
|
var transform = CATransform3DIdentity
|
||||||
|
transform = CATransform3DScale(transform, scale, scale, 0.0)
|
||||||
|
|
||||||
self.sourceNode.contentNode.transform = CATransform3DIdentity
|
self.sourceNode.contentNode.transform = CATransform3DIdentity
|
||||||
|
self.sourceNode.contentNode.position = CGPoint(x: initialSourceFrame.midX, y: initialSourceFrame.midY)
|
||||||
self.sourceNode.contentNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration * 1.2, damping: 110.0)
|
self.sourceNode.contentNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration * 1.2, damping: 110.0)
|
||||||
self.sourceNode.contentNode.layer.animatePosition(from: CGPoint(x: offset.x, y: offset.y), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, force: true, completion: { _ in
|
self.sourceNode.contentNode.layer.animatePosition(from: CGPoint(x: offset.x - pinchOffset.x * (scale - 1.0), y: offset.y - pinchOffset.y * (scale - 1.0)), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, force: true, completion: { _ in
|
||||||
performCompletion()
|
performCompletion()
|
||||||
})
|
})
|
||||||
|
|
||||||
let dimNodeTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
let dimNodeTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: transitionCurve)
|
||||||
dimNodeTransition.updateAlpha(node: self.dimNode, alpha: 0.0)
|
dimNodeTransition.updateAlpha(node: self.dimNode, alpha: 0.0)
|
||||||
} else {
|
} else {
|
||||||
performCompletion()
|
performCompletion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
||||||
|
if self.isAnimatingOut {
|
||||||
|
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: offset.y)
|
||||||
|
transition.animateOffsetAdditive(node: self.scrollingContainer, offset: -offset.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class PinchController: ViewController, StandalonePresentableController {
|
public final class PinchController: ViewController, StandalonePresentableController {
|
||||||
@ -335,6 +410,7 @@ public final class PinchController: ViewController, StandalonePresentableControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let sourceNode: PinchSourceContainerNode
|
private let sourceNode: PinchSourceContainerNode
|
||||||
|
private let getContentAreaInScreenSpace: () -> CGRect
|
||||||
|
|
||||||
private var wasDismissed = false
|
private var wasDismissed = false
|
||||||
|
|
||||||
@ -342,8 +418,9 @@ public final class PinchController: ViewController, StandalonePresentableControl
|
|||||||
return self.displayNode as! PinchControllerNode
|
return self.displayNode as! PinchControllerNode
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(sourceNode: PinchSourceContainerNode) {
|
public init(sourceNode: PinchSourceContainerNode, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||||
self.sourceNode = sourceNode
|
self.sourceNode = sourceNode
|
||||||
|
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -361,7 +438,7 @@ public final class PinchController: ViewController, StandalonePresentableControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = PinchControllerNode(controller: self, sourceNode: self.sourceNode)
|
self.displayNode = PinchControllerNode(controller: self, sourceNode: self.sourceNode, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
|
||||||
|
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
@ -392,4 +469,8 @@ public final class PinchController: ViewController, StandalonePresentableControl
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.controllerNode.addRelativeContentOffset(offset, transition: transition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case skipReadHistory(PresentationTheme, Bool)
|
case skipReadHistory(PresentationTheme, Bool)
|
||||||
case crashOnSlowQueries(PresentationTheme, Bool)
|
case crashOnSlowQueries(PresentationTheme, Bool)
|
||||||
case clearTips(PresentationTheme)
|
case clearTips(PresentationTheme)
|
||||||
case reimport(PresentationTheme)
|
case crash(PresentationTheme)
|
||||||
case resetData(PresentationTheme)
|
case resetData(PresentationTheme)
|
||||||
case resetDatabase(PresentationTheme)
|
case resetDatabase(PresentationTheme)
|
||||||
case resetDatabaseAndCache(PresentationTheme)
|
case resetDatabaseAndCache(PresentationTheme)
|
||||||
@ -74,6 +74,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case knockoutWallpaper(PresentationTheme, Bool)
|
case knockoutWallpaper(PresentationTheme, Bool)
|
||||||
case demoVideoChats(Bool)
|
case demoVideoChats(Bool)
|
||||||
case experimentalCompatibility(Bool)
|
case experimentalCompatibility(Bool)
|
||||||
|
case enableNoiseSuppression(Bool)
|
||||||
case playerEmbedding(Bool)
|
case playerEmbedding(Bool)
|
||||||
case playlistPlayback(Bool)
|
case playlistPlayback(Bool)
|
||||||
case voiceConference
|
case voiceConference
|
||||||
@ -93,7 +94,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .experimentalCompatibility, .playerEmbedding, .playlistPlayback, .voiceConference:
|
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableNoiseSuppression:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .preferredVideoCodec:
|
case .preferredVideoCodec:
|
||||||
return DebugControllerSection.videoExperiments.rawValue
|
return DebugControllerSection.videoExperiments.rawValue
|
||||||
@ -134,7 +135,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 12
|
return 12
|
||||||
case .clearTips:
|
case .clearTips:
|
||||||
return 13
|
return 13
|
||||||
case .reimport:
|
case .crash:
|
||||||
return 14
|
return 14
|
||||||
case .resetData:
|
case .resetData:
|
||||||
return 15
|
return 15
|
||||||
@ -158,14 +159,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 24
|
return 24
|
||||||
case .experimentalCompatibility:
|
case .experimentalCompatibility:
|
||||||
return 25
|
return 25
|
||||||
case .playerEmbedding:
|
case .enableNoiseSuppression:
|
||||||
return 26
|
return 26
|
||||||
case .playlistPlayback:
|
case .playerEmbedding:
|
||||||
return 27
|
return 27
|
||||||
case .voiceConference:
|
case .playlistPlayback:
|
||||||
return 28
|
return 28
|
||||||
|
case .voiceConference:
|
||||||
|
return 29
|
||||||
case let .preferredVideoCodec(index, _, _, _):
|
case let .preferredVideoCodec(index, _, _, _):
|
||||||
return 29 + index
|
return 30 + index
|
||||||
case .disableVideoAspectScaling:
|
case .disableVideoAspectScaling:
|
||||||
return 100
|
return 100
|
||||||
case .enableVoipTcp:
|
case .enableVoipTcp:
|
||||||
@ -553,20 +556,9 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .reimport(theme):
|
case let .crash(theme):
|
||||||
return ItemListActionItem(presentationData: presentationData, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
let appGroupName = "group.\(Bundle.main.bundleIdentifier!)"
|
preconditionFailure()
|
||||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
|
||||||
|
|
||||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let statusPath = appGroupUrl.path + "/Documents/importcompleted"
|
|
||||||
if FileManager.default.fileExists(atPath: statusPath) {
|
|
||||||
let _ = try? FileManager.default.removeItem(at: URL(fileURLWithPath: statusPath))
|
|
||||||
exit(0)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
case let .resetData(theme):
|
case let .resetData(theme):
|
||||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: "Reset Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
@ -725,6 +717,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
|
case let .enableNoiseSuppression(value):
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, title: "Noise Suppression", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||||
|
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
|
||||||
|
settings.enableNoiseSuppression = value
|
||||||
|
return settings
|
||||||
|
})
|
||||||
|
}).start()
|
||||||
|
})
|
||||||
case let .playerEmbedding(value):
|
case let .playerEmbedding(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
@ -822,6 +824,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
if isMainApp {
|
if isMainApp {
|
||||||
entries.append(.clearTips(presentationData.theme))
|
entries.append(.clearTips(presentationData.theme))
|
||||||
}
|
}
|
||||||
|
entries.append(.crash(presentationData.theme))
|
||||||
entries.append(.resetData(presentationData.theme))
|
entries.append(.resetData(presentationData.theme))
|
||||||
entries.append(.resetDatabase(presentationData.theme))
|
entries.append(.resetDatabase(presentationData.theme))
|
||||||
entries.append(.resetDatabaseAndCache(presentationData.theme))
|
entries.append(.resetDatabaseAndCache(presentationData.theme))
|
||||||
@ -834,6 +837,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
|
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
|
||||||
entries.append(.demoVideoChats(experimentalSettings.demoVideoChats))
|
entries.append(.demoVideoChats(experimentalSettings.demoVideoChats))
|
||||||
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
|
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
|
||||||
|
entries.append(.enableNoiseSuppression(experimentalSettings.enableNoiseSuppression))
|
||||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -563,7 +563,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
guard let strongSelf = self, let controller = strongSelf.controller else {
|
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let pinchController = PinchController(sourceNode: sourceNode)
|
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
|
||||||
|
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||||
|
return CGRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller.view.convert(controller.view.bounds, to: nil)
|
||||||
|
})
|
||||||
controller.window?.presentInGlobalOverlay(pinchController)
|
controller.window?.presentInGlobalOverlay(pinchController)
|
||||||
}, openPeer: { [weak self] peerId in
|
}, openPeer: { [weak self] peerId in
|
||||||
self?.openPeer(peerId)
|
self?.openPeer(peerId)
|
||||||
|
|||||||
@ -235,9 +235,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
|
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
|
||||||
let path = basePath + "/db_sqlite"
|
let path = basePath + "/db_sqlite"
|
||||||
|
|
||||||
#if DEBUG
|
postboxLog("Instance \(self) opening sqlite at \(path)")
|
||||||
print("Instance \(self) opening sqlite at \(path)")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
let exists = FileManager.default.fileExists(atPath: path)
|
let exists = FileManager.default.fileExists(atPath: path)
|
||||||
@ -298,7 +296,9 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
preconditionFailure("Couldn't open database")
|
preconditionFailure("Couldn't open database")
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_busy_timeout(database.handle, 1000 * 10000)
|
postboxLog("Did open DB at \(path)")
|
||||||
|
|
||||||
|
sqlite3_busy_timeout(database.handle, 5 * 1000)
|
||||||
|
|
||||||
var resultCode: Bool = true
|
var resultCode: Bool = true
|
||||||
|
|
||||||
@ -307,7 +307,11 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
|
resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
|
||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
|
|
||||||
|
postboxLog("Did set up cipher")
|
||||||
|
|
||||||
if self.isEncrypted(database) {
|
if self.isEncrypted(database) {
|
||||||
|
postboxLog("Database is encrypted")
|
||||||
|
|
||||||
if let encryptionParameters = encryptionParameters {
|
if let encryptionParameters = encryptionParameters {
|
||||||
precondition(encryptionParameters.salt.data.count == 16)
|
precondition(encryptionParameters.salt.data.count == 16)
|
||||||
precondition(encryptionParameters.key.data.count == 32)
|
precondition(encryptionParameters.key.data.count == 32)
|
||||||
@ -317,11 +321,14 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
|
resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
|
||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
|
|
||||||
|
postboxLog("Setting encryption key")
|
||||||
|
|
||||||
if self.isEncrypted(database) {
|
if self.isEncrypted(database) {
|
||||||
|
postboxLog("Encryption key is invalid")
|
||||||
|
|
||||||
if isTemporary || isReadOnly {
|
if isTemporary || isReadOnly {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
postboxLog("Encryption key is invalid")
|
|
||||||
|
|
||||||
for fileName in dabaseFileNames {
|
for fileName in dabaseFileNames {
|
||||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||||
@ -354,6 +361,8 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
}
|
}
|
||||||
} else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet {
|
} else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet {
|
||||||
|
postboxLog("Not encrypted")
|
||||||
|
|
||||||
let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)
|
let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: path) {
|
if FileManager.default.fileExists(atPath: path) {
|
||||||
@ -410,6 +419,8 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postboxLog("Did set up encryption")
|
||||||
|
|
||||||
//database.execute("PRAGMA cache_size=-2097152")
|
//database.execute("PRAGMA cache_size=-2097152")
|
||||||
resultCode = database.execute("PRAGMA mmap_size=0")
|
resultCode = database.execute("PRAGMA mmap_size=0")
|
||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
@ -421,6 +432,9 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
resultCode = database.execute("PRAGMA cipher_memory_security = OFF")
|
resultCode = database.execute("PRAGMA cipher_memory_security = OFF")
|
||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
|
|
||||||
|
postboxLog("Did set up pragmas")
|
||||||
|
|
||||||
//resultCode = database.execute("PRAGMA wal_autocheckpoint=500")
|
//resultCode = database.execute("PRAGMA wal_autocheckpoint=500")
|
||||||
//database.execute("PRAGMA journal_size_limit=1536")
|
//database.execute("PRAGMA journal_size_limit=1536")
|
||||||
|
|
||||||
@ -442,8 +456,12 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
let _ = self.runPragma(database, "checkpoint_fullfsync = 1")
|
let _ = self.runPragma(database, "checkpoint_fullfsync = 1")
|
||||||
assert(self.runPragma(database, "checkpoint_fullfsync") == "1")
|
assert(self.runPragma(database, "checkpoint_fullfsync") == "1")
|
||||||
|
|
||||||
|
postboxLog("Did set up checkpoint_fullfsync")
|
||||||
|
|
||||||
self.beginInternal(database: database)
|
self.beginInternal(database: database)
|
||||||
|
|
||||||
|
postboxLog("Did begin transaction")
|
||||||
|
|
||||||
let result = self.getUserVersion(database)
|
let result = self.getUserVersion(database)
|
||||||
|
|
||||||
if result < 3 {
|
if result < 3 {
|
||||||
@ -463,8 +481,12 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
self.fullTextTables[table.id] = table
|
self.fullTextTables[table.id] = table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postboxLog("Did load tables")
|
||||||
|
|
||||||
self.commitInternal(database: database)
|
self.commitInternal(database: database)
|
||||||
|
|
||||||
|
postboxLog("Did commit final")
|
||||||
|
|
||||||
lock.unlock()
|
lock.unlock()
|
||||||
|
|
||||||
return database
|
return database
|
||||||
@ -518,7 +540,21 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
|
|
||||||
private func isEncrypted(_ database: Database) -> Bool {
|
private func isEncrypted(_ database: Database) -> Bool {
|
||||||
var statement: OpaquePointer? = nil
|
var statement: OpaquePointer? = nil
|
||||||
|
postboxLog("isEncrypted prepare...")
|
||||||
|
|
||||||
|
let allIsOk = Atomic<Bool>(value: false)
|
||||||
|
let databasePath = self.databasePath
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + 5.0, execute: {
|
||||||
|
if allIsOk.with({ $0 }) == false {
|
||||||
|
postboxLog("Timeout reached, discarding database")
|
||||||
|
try? FileManager.default.removeItem(atPath: databasePath)
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil)
|
let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil)
|
||||||
|
let _ = allIsOk.swap(true)
|
||||||
|
postboxLog("isEncrypted prepare done")
|
||||||
if statement == nil {
|
if statement == nil {
|
||||||
postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]")
|
postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]")
|
||||||
return true
|
return true
|
||||||
@ -536,6 +572,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
preparedStatement.destroy()
|
preparedStatement.destroy()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
postboxLog("isEncrypted step done")
|
||||||
preparedStatement.destroy()
|
preparedStatement.destroy()
|
||||||
return status == SQLITE_NOTADB
|
return status == SQLITE_NOTADB
|
||||||
}
|
}
|
||||||
|
|||||||
@ -347,7 +347,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
var title: String = ""
|
var title: String = ""
|
||||||
var speakerSubtitle: String = ""
|
var speakerSubtitle: String = ""
|
||||||
|
|
||||||
let textFont = Font.regular(13.0)
|
let textFont = Font.with(size: 13.0, design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||||
let textColor = UIColor.white
|
let textColor = UIColor.white
|
||||||
var segments: [AnimatedCountLabelNode.Segment] = []
|
var segments: [AnimatedCountLabelNode.Segment] = []
|
||||||
var displaySpeakerSubtitle = false
|
var displaySpeakerSubtitle = false
|
||||||
@ -381,7 +381,22 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
}
|
}
|
||||||
displaySpeakerSubtitle = speakerSubtitle != title && !speakerSubtitle.isEmpty
|
displaySpeakerSubtitle = speakerSubtitle != title && !speakerSubtitle.isEmpty
|
||||||
|
|
||||||
if let membersCount = membersCount {
|
var requiresTimer = false
|
||||||
|
if let scheduleTime = self.currentGroupCallState?.info?.scheduleTimestamp {
|
||||||
|
requiresTimer = true
|
||||||
|
|
||||||
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
let elapsedTime = scheduleTime - currentTime
|
||||||
|
let timerText: String
|
||||||
|
if elapsedTime >= 86400 {
|
||||||
|
timerText = timeIntervalString(strings: presentationData.strings, value: elapsedTime)
|
||||||
|
} else if elapsedTime < 0 {
|
||||||
|
timerText = presentationData.strings.VoiceChat_StatusLateBy(textForTimeout(value: abs(elapsedTime))).0
|
||||||
|
} else {
|
||||||
|
timerText = presentationData.strings.VoiceChat_StatusStartsIn(textForTimeout(value: elapsedTime)).0
|
||||||
|
}
|
||||||
|
segments.append(.text(0, NSAttributedString(string: timerText, font: textFont, textColor: textColor)))
|
||||||
|
} else if let membersCount = membersCount {
|
||||||
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
|
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
|
||||||
if membersPart.contains("[") && membersPart.contains("]") {
|
if membersPart.contains("[") && membersPart.contains("]") {
|
||||||
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
|
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
|
||||||
@ -433,6 +448,19 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundNode.connectingColor = color
|
self.backgroundNode.connectingColor = color
|
||||||
|
|
||||||
|
if requiresTimer {
|
||||||
|
if self.currentCallTimer == nil {
|
||||||
|
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||||
|
self?.update()
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
timer.start()
|
||||||
|
self.currentCallTimer = timer
|
||||||
|
}
|
||||||
|
} else if let currentCallTimer = self.currentCallTimer {
|
||||||
|
self.currentCallTimer = nil
|
||||||
|
currentCallTimer.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.subtitleNode.segments != segments && !displaySpeakerSubtitle {
|
if self.subtitleNode.segments != segments && !displaySpeakerSubtitle {
|
||||||
|
|||||||
@ -525,7 +525,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
if elapsedTime >= 86400 {
|
if elapsedTime >= 86400 {
|
||||||
joinText = timeIntervalString(strings: strings, value: elapsedTime)
|
joinText = timeIntervalString(strings: strings, value: elapsedTime)
|
||||||
} else if elapsedTime < 0 {
|
} else if elapsedTime < 0 {
|
||||||
joinText = "+\(textForTimeout(value: abs(elapsedTime)))"
|
joinText = "-\(textForTimeout(value: abs(elapsedTime)))"
|
||||||
} else {
|
} else {
|
||||||
joinText = textForTimeout(value: elapsedTime)
|
joinText = textForTimeout(value: elapsedTime)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -557,6 +557,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
private var peerUpdatesSubscription: Disposable?
|
private var peerUpdatesSubscription: Disposable?
|
||||||
|
|
||||||
public private(set) var schedulePending = false
|
public private(set) var schedulePending = false
|
||||||
|
private var isScheduled = false
|
||||||
|
private var isScheduledStarted = false
|
||||||
|
|
||||||
init(
|
init(
|
||||||
accountContext: AccountContext,
|
accountContext: AccountContext,
|
||||||
@ -581,6 +583,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.invite = invite
|
self.invite = invite
|
||||||
self.joinAsPeerId = joinAsPeerId ?? accountContext.account.peerId
|
self.joinAsPeerId = joinAsPeerId ?? accountContext.account.peerId
|
||||||
self.schedulePending = initialCall == nil
|
self.schedulePending = initialCall == nil
|
||||||
|
self.isScheduled = initialCall == nil || initialCall?.scheduleTimestamp != nil
|
||||||
|
|
||||||
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false)
|
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false)
|
||||||
self.statePromise = ValuePromise(self.stateValue)
|
self.statePromise = ValuePromise(self.stateValue)
|
||||||
@ -1058,6 +1061,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
case let .active(previousCallInfo):
|
case let .active(previousCallInfo):
|
||||||
if case let .active(callInfo) = internalState {
|
if case let .active(callInfo) = internalState {
|
||||||
shouldJoin = previousCallInfo.scheduleTimestamp != nil && callInfo.scheduleTimestamp == nil
|
shouldJoin = previousCallInfo.scheduleTimestamp != nil && callInfo.scheduleTimestamp == nil
|
||||||
|
self.participantsContext = nil
|
||||||
activeCallInfo = callInfo
|
activeCallInfo = callInfo
|
||||||
} else {
|
} else {
|
||||||
activeCallInfo = nil
|
activeCallInfo = nil
|
||||||
@ -1082,6 +1086,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
outgoingAudioBitrateKbit = value
|
outgoingAudioBitrateKbit = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let enableNoiseSuppression = accountContext.sharedContext.immediateExperimentalUISettings.enableNoiseSuppression
|
||||||
|
|
||||||
callContext = OngoingGroupCallContext(video: self.videoCapturer, participantDescriptionsRequired: { [weak self] ssrcs in
|
callContext = OngoingGroupCallContext(video: self.videoCapturer, participantDescriptionsRequired: { [weak self] ssrcs in
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -1098,7 +1104,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: self.isVideo)
|
}, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: self.isVideo, enableNoiseSuppression: enableNoiseSuppression)
|
||||||
self.incomingVideoSourcePromise.set(callContext.videoSources
|
self.incomingVideoSourcePromise.set(callContext.videoSources
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { [weak self] sources -> [PeerId: UInt32] in
|
|> map { [weak self] sources -> [PeerId: UInt32] in
|
||||||
@ -1284,10 +1290,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
if !strongSelf.didConnectOnce {
|
if !strongSelf.didConnectOnce {
|
||||||
strongSelf.didConnectOnce = true
|
strongSelf.didConnectOnce = true
|
||||||
|
|
||||||
|
if !strongSelf.isScheduled {
|
||||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupJoined)
|
let toneRenderer = PresentationCallToneRenderer(tone: .groupJoined)
|
||||||
strongSelf.toneRenderer = toneRenderer
|
strongSelf.toneRenderer = toneRenderer
|
||||||
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let peer = strongSelf.reconnectingAsPeer {
|
if let peer = strongSelf.reconnectingAsPeer {
|
||||||
strongSelf.reconnectingAsPeer = nil
|
strongSelf.reconnectingAsPeer = nil
|
||||||
@ -1711,6 +1719,180 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
|
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
|
||||||
self.startCheckingCallIfNeeded()
|
self.startCheckingCallIfNeeded()
|
||||||
}
|
}
|
||||||
|
} else if case let .active(callInfo) = internalState, callInfo.scheduleTimestamp != nil {
|
||||||
|
let accountContext = self.accountContext
|
||||||
|
let peerId = self.peerId
|
||||||
|
let rawAdminIds: Signal<Set<PeerId>, NoError>
|
||||||
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
rawAdminIds = Signal { subscriber in
|
||||||
|
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||||
|
var peerIds = Set<PeerId>()
|
||||||
|
for item in list.list {
|
||||||
|
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
|
||||||
|
peerIds.insert(item.peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriber.putNext(peerIds)
|
||||||
|
})
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> runOn(.mainQueue())
|
||||||
|
} else {
|
||||||
|
rawAdminIds = accountContext.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)])
|
||||||
|
|> map { views -> Set<PeerId> in
|
||||||
|
guard let view = views.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView else {
|
||||||
|
return Set()
|
||||||
|
}
|
||||||
|
guard let cachedData = view.cachedPeerData as? CachedGroupData, let participants = cachedData.participants else {
|
||||||
|
return Set()
|
||||||
|
}
|
||||||
|
return Set(participants.participants.compactMap { item -> PeerId? in
|
||||||
|
switch item {
|
||||||
|
case .creator, .admin:
|
||||||
|
return item.peerId
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
let adminIds = combineLatest(queue: .mainQueue(),
|
||||||
|
rawAdminIds,
|
||||||
|
accountContext.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||||
|
)
|
||||||
|
|> map { rawAdminIds, view -> Set<PeerId> in
|
||||||
|
var rawAdminIds = rawAdminIds
|
||||||
|
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer as? TelegramChannel {
|
||||||
|
if peer.hasPermission(.manageCalls) {
|
||||||
|
rawAdminIds.insert(accountContext.account.peerId)
|
||||||
|
} else {
|
||||||
|
rawAdminIds.remove(accountContext.account.peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawAdminIds
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
|
let participantsContext = GroupCallParticipantsContext(
|
||||||
|
account: self.accountContext.account,
|
||||||
|
peerId: self.peerId,
|
||||||
|
myPeerId: self.joinAsPeerId,
|
||||||
|
id: callInfo.id,
|
||||||
|
accessHash: callInfo.accessHash,
|
||||||
|
state: GroupCallParticipantsContext.State(
|
||||||
|
participants: [],
|
||||||
|
nextParticipantsFetchOffset: nil,
|
||||||
|
adminIds: Set(),
|
||||||
|
isCreator: false,
|
||||||
|
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: self.stateValue.defaultParticipantMuteState == .muted, canChange: false),
|
||||||
|
sortAscending: true,
|
||||||
|
recordingStartTimestamp: nil,
|
||||||
|
title: self.stateValue.title,
|
||||||
|
scheduleTimestamp: self.stateValue.scheduleTimestamp,
|
||||||
|
subscribedToScheduled: self.stateValue.subscribedToScheduled,
|
||||||
|
totalCount: 0,
|
||||||
|
version: 0
|
||||||
|
),
|
||||||
|
previousServiceState: nil
|
||||||
|
)
|
||||||
|
self.temporaryParticipantsContext = nil
|
||||||
|
self.participantsContext = participantsContext
|
||||||
|
|
||||||
|
let myPeerId = self.joinAsPeerId
|
||||||
|
let myPeer = self.accountContext.account.postbox.transaction { transaction -> (Peer, CachedPeerData?)? in
|
||||||
|
if let peer = transaction.getPeer(myPeerId) {
|
||||||
|
return (peer, transaction.getPeerCachedData(peerId: myPeerId))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||||
|
participantsContext.state,
|
||||||
|
adminIds,
|
||||||
|
myPeer,
|
||||||
|
accountContext.account.postbox.peerView(id: peerId)
|
||||||
|
).start(next: { [weak self] state, adminIds, myPeerAndCachedData, view in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var members = PresentationGroupCallMembers(
|
||||||
|
participants: [],
|
||||||
|
speakingParticipants: Set(),
|
||||||
|
totalCount: state.totalCount,
|
||||||
|
loadMoreToken: state.nextParticipantsFetchOffset
|
||||||
|
)
|
||||||
|
|
||||||
|
var participants: [GroupCallParticipantsContext.Participant] = []
|
||||||
|
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||||
|
if let (myPeer, cachedData) = myPeerAndCachedData {
|
||||||
|
let about: String?
|
||||||
|
if let cachedData = cachedData as? CachedUserData {
|
||||||
|
about = cachedData.about
|
||||||
|
} else if let cachedData = cachedData as? CachedUserData {
|
||||||
|
about = cachedData.about
|
||||||
|
} else {
|
||||||
|
about = nil
|
||||||
|
}
|
||||||
|
participants.append(GroupCallParticipantsContext.Participant(
|
||||||
|
peer: myPeer,
|
||||||
|
ssrc: nil,
|
||||||
|
jsonParams: nil,
|
||||||
|
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||||
|
raiseHandRating: strongSelf.temporaryRaiseHandRating,
|
||||||
|
hasRaiseHand: strongSelf.temporaryHasRaiseHand,
|
||||||
|
activityTimestamp: strongSelf.temporaryActivityTimestamp,
|
||||||
|
activityRank: strongSelf.temporaryActivityRank,
|
||||||
|
muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||||
|
volume: nil,
|
||||||
|
about: about
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
for participant in participants {
|
||||||
|
members.participants.append(participant)
|
||||||
|
|
||||||
|
if topParticipants.count < 3 {
|
||||||
|
topParticipants.append(participant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.membersValue = members
|
||||||
|
strongSelf.stateValue.adminIds = adminIds
|
||||||
|
strongSelf.stateValue.canManageCall = state.isCreator || adminIds.contains(strongSelf.accountContext.account.peerId)
|
||||||
|
if (state.isCreator || strongSelf.stateValue.adminIds.contains(strongSelf.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange {
|
||||||
|
strongSelf.stateValue.defaultParticipantMuteState = state.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
|
||||||
|
}
|
||||||
|
strongSelf.stateValue.recordingStartTimestamp = state.recordingStartTimestamp
|
||||||
|
strongSelf.stateValue.title = state.title
|
||||||
|
|
||||||
|
strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp
|
||||||
|
if state.scheduleTimestamp == nil && !strongSelf.isScheduledStarted {
|
||||||
|
strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, clientParams: callInfo.clientParams, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true)), audioSessionControl: strongSelf.audioSessionControl)
|
||||||
|
} else {
|
||||||
|
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
||||||
|
id: callInfo.id,
|
||||||
|
accessHash: callInfo.accessHash,
|
||||||
|
participantCount: state.totalCount,
|
||||||
|
clientParams: nil,
|
||||||
|
streamDcId: nil,
|
||||||
|
title: state.title,
|
||||||
|
scheduleTimestamp: state.scheduleTimestamp,
|
||||||
|
subscribedToScheduled: false,
|
||||||
|
recordingStartTimestamp: state.recordingStartTimestamp,
|
||||||
|
sortAscending: state.sortAscending
|
||||||
|
))))
|
||||||
|
|
||||||
|
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
|
participantCount: state.totalCount,
|
||||||
|
topParticipants: topParticipants,
|
||||||
|
activeSpeakers: Set()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1886,18 +2068,22 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
return transaction.getPeer(peerId)
|
return transaction.getPeer(peerId)
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] myPeer in
|
|> deliverOnMainQueue).start(next: { [weak self] myPeer in
|
||||||
guard let strongSelf = self, let _ = myPeer else {
|
guard let strongSelf = self, let myPeer = myPeer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.reconnectingAsPeer = myPeer
|
|
||||||
|
|
||||||
let previousPeerId = strongSelf.joinAsPeerId
|
let previousPeerId = strongSelf.joinAsPeerId
|
||||||
if let localSsrc = strongSelf.currentLocalSsrc {
|
if let localSsrc = strongSelf.currentLocalSsrc {
|
||||||
strongSelf.ignorePreviousJoinAsPeerId = (previousPeerId, localSsrc)
|
strongSelf.ignorePreviousJoinAsPeerId = (previousPeerId, localSsrc)
|
||||||
}
|
}
|
||||||
strongSelf.joinAsPeerId = peerId
|
strongSelf.joinAsPeerId = peerId
|
||||||
|
|
||||||
|
if strongSelf.stateValue.scheduleTimestamp != nil {
|
||||||
|
strongSelf.stateValue.myPeerId = peerId
|
||||||
|
strongSelf.reconnectedAsEventsPipe.putNext(myPeer)
|
||||||
|
} else {
|
||||||
|
strongSelf.reconnectingAsPeer = myPeer
|
||||||
|
|
||||||
if let participantsContext = strongSelf.participantsContext, let immediateState = participantsContext.immediateState {
|
if let participantsContext = strongSelf.participantsContext, let immediateState = participantsContext.immediateState {
|
||||||
for participant in immediateState.participants {
|
for participant in immediateState.participants {
|
||||||
if participant.peer.id == previousPeerId {
|
if participant.peer.id == previousPeerId {
|
||||||
@ -1915,6 +2101,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2011,20 +2198,32 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.schedulePending = false
|
self.schedulePending = false
|
||||||
self.stateValue.scheduleTimestamp = timestamp
|
self.stateValue.scheduleTimestamp = timestamp
|
||||||
|
|
||||||
|
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
|
participantCount: 1,
|
||||||
|
topParticipants: [],
|
||||||
|
activeSpeakers: Set()
|
||||||
|
)))
|
||||||
|
|
||||||
self.startDisposable.set((createGroupCall(account: self.account, peerId: self.peerId, title: nil, scheduleDate: timestamp)
|
self.startDisposable.set((createGroupCall(account: self.account, peerId: self.peerId, title: nil, scheduleDate: timestamp)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] callInfo in
|
|> deliverOnMainQueue).start(next: { [weak self] callInfo in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.updateSessionState(internalState: .active(callInfo), audioSessionControl: strongSelf.audioSessionControl)
|
strongSelf.updateSessionState(internalState: .active(callInfo), audioSessionControl: strongSelf.audioSessionControl)
|
||||||
|
}, error: { [weak self] error in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.markAsCanBeRemoved()
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public func startScheduled() {
|
public func startScheduled() {
|
||||||
guard case let .active(callInfo) = self.internalState else {
|
guard case let .active(callInfo) = self.internalState else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isScheduledStarted = true
|
||||||
self.stateValue.scheduleTimestamp = nil
|
self.stateValue.scheduleTimestamp = nil
|
||||||
|
|
||||||
self.startDisposable.set((startScheduledGroupCall(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash)
|
self.startDisposable.set((startScheduledGroupCall(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash)
|
||||||
@ -2033,6 +2232,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.updateSessionState(internalState: .active(callInfo), audioSessionControl: strongSelf.audioSessionControl)
|
strongSelf.updateSessionState(internalState: .active(callInfo), audioSessionControl: strongSelf.audioSessionControl)
|
||||||
|
|
||||||
|
let toneRenderer = PresentationCallToneRenderer(tone: .groupJoined)
|
||||||
|
strongSelf.toneRenderer = toneRenderer
|
||||||
|
toneRenderer.setAudioSessionActive(strongSelf.isAudioSessionActive)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2244,7 +2447,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let account = self.account
|
let account = self.account
|
||||||
|
|
||||||
let currentCall: Signal<GroupCallInfo?, CallError>
|
let currentCall: Signal<GroupCallInfo?, CallError>
|
||||||
if let initialCall = self.initialCall {
|
if let initialCall = self.initialCall {
|
||||||
currentCall = getCurrentGroupCall(account: account, callId: initialCall.id, accessHash: initialCall.accessHash)
|
currentCall = getCurrentGroupCall(account: account, callId: initialCall.id, accessHash: initialCall.accessHash)
|
||||||
@ -2254,6 +2456,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|> map { summary -> GroupCallInfo? in
|
|> map { summary -> GroupCallInfo? in
|
||||||
return summary?.info
|
return summary?.info
|
||||||
}
|
}
|
||||||
|
} else if case let .active(callInfo) = self.internalState {
|
||||||
|
currentCall = getCurrentGroupCall(account: account, callId: callInfo.id, accessHash: callInfo.accessHash)
|
||||||
|
|> mapError { _ -> CallError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> map { summary -> GroupCallInfo? in
|
||||||
|
return summary?.info
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
currentCall = .single(nil)
|
currentCall = .single(nil)
|
||||||
}
|
}
|
||||||
@ -2319,7 +2529,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
guard let callInfo = self.internalState.callInfo else {
|
guard let callInfo = self.internalState.callInfo else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.stateValue.title = title
|
self.stateValue.title = title.isEmpty ? nil : title
|
||||||
let _ = editGroupCallTitle(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, title: title).start()
|
let _ = editGroupCallTitle(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, title: title).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -242,7 +242,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||||
|
|
||||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - totalHeight) / 2.0) + 88.0), size: titleSize)
|
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - totalHeight) / 2.0) + 84.0), size: titleSize)
|
||||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||||
|
|
||||||
self.bottomNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.bottomNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
@ -361,6 +361,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var backgroundState: VoiceChatActionButtonBackgroundNode.State
|
var backgroundState: VoiceChatActionButtonBackgroundNode.State
|
||||||
|
var animated = true
|
||||||
switch state {
|
switch state {
|
||||||
case let .button(text):
|
case let .button(text):
|
||||||
backgroundState = .button
|
backgroundState = .button
|
||||||
@ -370,6 +371,9 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
self.buttonTitleLabel.frame = CGRect(origin: CGPoint(x: floor((self.bounds.width - titleSize.width) / 2.0), y: floor((self.bounds.height - titleSize.height) / 2.0)), size: titleSize)
|
self.buttonTitleLabel.frame = CGRect(origin: CGPoint(x: floor((self.bounds.width - titleSize.width) / 2.0), y: floor((self.bounds.height - titleSize.height) / 2.0)), size: titleSize)
|
||||||
case .scheduled:
|
case .scheduled:
|
||||||
backgroundState = .disabled
|
backgroundState = .disabled
|
||||||
|
if previousState == .connecting {
|
||||||
|
animated = false
|
||||||
|
}
|
||||||
case let .active(state):
|
case let .active(state):
|
||||||
switch state {
|
switch state {
|
||||||
case .on:
|
case .on:
|
||||||
@ -385,7 +389,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
self.applyIconParams()
|
self.applyIconParams()
|
||||||
|
|
||||||
self.backgroundNode.isDark = dark
|
self.backgroundNode.isDark = dark
|
||||||
self.backgroundNode.update(state: backgroundState, animated: true)
|
self.backgroundNode.update(state: backgroundState, animated: animated)
|
||||||
|
|
||||||
if case .active = state, let previousState = previousState, case .connecting = previousState, animated {
|
if case .active = state, let previousState = previousState, case .connecting = previousState, animated {
|
||||||
self.activeDisposable.set((self.activePromise.get()
|
self.activeDisposable.set((self.activePromise.get()
|
||||||
@ -755,7 +759,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
case muted
|
case muted
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGlowAndGradientAnimations(type: Gradient, previousType: Gradient? = nil) {
|
func updateGlowAndGradientAnimations(type: Gradient, previousType: Gradient? = nil, animated: Bool = true) {
|
||||||
let effectivePreviousTyoe = previousType ?? .active
|
let effectivePreviousTyoe = previousType ?? .active
|
||||||
|
|
||||||
let scale: CGFloat
|
let scale: CGFloat
|
||||||
@ -794,13 +798,15 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self.maskGradientLayer.transform = CATransform3DMakeScale(targetScale, targetScale, 1.0)
|
self.maskGradientLayer.transform = CATransform3DMakeScale(targetScale, targetScale, 1.0)
|
||||||
if let _ = previousType {
|
if let _ = previousType {
|
||||||
self.maskGradientLayer.animateScale(from: initialScale, to: targetScale, duration: 0.3)
|
self.maskGradientLayer.animateScale(from: initialScale, to: targetScale, duration: 0.3)
|
||||||
} else {
|
} else if animated {
|
||||||
self.maskGradientLayer.animateSpring(from: initialScale as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
self.maskGradientLayer.animateSpring(from: initialScale as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.foregroundGradientLayer.colors = targetColors
|
self.foregroundGradientLayer.colors = targetColors
|
||||||
|
if animated {
|
||||||
self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func playMuteAnimation() {
|
private func playMuteAnimation() {
|
||||||
self.maskBlobView.startAnimating()
|
self.maskBlobView.startAnimating()
|
||||||
@ -1081,6 +1087,16 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self.playMuteAnimation()
|
self.playMuteAnimation()
|
||||||
}
|
}
|
||||||
self.transition = nil
|
self.transition = nil
|
||||||
|
} else {
|
||||||
|
if self.maskBlobView.isHidden {
|
||||||
|
self.updateGlowAndGradientAnimations(type: .muted, previousType: nil, animated: false)
|
||||||
|
self.maskCircleLayer.isHidden = false
|
||||||
|
self.maskProgressLayer.isHidden = true
|
||||||
|
self.maskGradientLayer.isHidden = false
|
||||||
|
self.maskBlobView.isHidden = false
|
||||||
|
self.maskBlobView.startAnimating()
|
||||||
|
self.maskBlobView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .button:
|
case .button:
|
||||||
self.updatedActive?(true)
|
self.updatedActive?(true)
|
||||||
@ -1537,7 +1553,6 @@ final class VoiceChatActionButtonIconNode: ManagedAnimationNode {
|
|||||||
self.isColored = isColored
|
self.isColored = isColored
|
||||||
super.init(size: CGSize(width: 100.0, height: 100.0))
|
super.init(size: CGSize(width: 100.0, height: 100.0))
|
||||||
|
|
||||||
self.scale = 0.8
|
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceUnmute"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1))
|
self.trackTo(item: ManagedAnimationItem(source: .local("VoiceUnmute"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1632,15 +1647,25 @@ final class VoiceChatActionButtonIconNode: ManagedAnimationNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var useTiredAnimation = false
|
var useTiredAnimation = false
|
||||||
|
var useAngryAnimation = false
|
||||||
let val = Float.random(in: 0.0..<1.0)
|
let val = Float.random(in: 0.0..<1.0)
|
||||||
if val <= 0.01 {
|
if val <= 0.01 {
|
||||||
useTiredAnimation = true
|
useTiredAnimation = true
|
||||||
|
} else if val <= 0.05 {
|
||||||
|
useAngryAnimation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let normalAnimations = ["VoiceHand_1", "VoiceHand_2", "VoiceHand_3", "VoiceHand_4", "VoiceHand_7"]
|
let normalAnimations = ["VoiceHand_1", "VoiceHand_2", "VoiceHand_3", "VoiceHand_4", "VoiceHand_7", "VoiceHand_8"]
|
||||||
let tiredAnimations = ["VoiceHand_5", "VoiceHand_6"]
|
let tiredAnimations = ["VoiceHand_5", "VoiceHand_6"]
|
||||||
let animations = useTiredAnimation ? tiredAnimations : normalAnimations
|
let angryAnimations = ["VoiceHand_9", "VoiceHand_10"]
|
||||||
|
let animations: [String]
|
||||||
|
if useTiredAnimation {
|
||||||
|
animations = tiredAnimations
|
||||||
|
} else if useAngryAnimation {
|
||||||
|
animations = angryAnimations
|
||||||
|
} else {
|
||||||
|
animations = normalAnimations
|
||||||
|
}
|
||||||
if let animationName = animations.randomElement() {
|
if let animationName = animations.randomElement() {
|
||||||
self.trackTo(item: ManagedAnimationItem(source: .local(animationName)))
|
self.trackTo(item: ManagedAnimationItem(source: .local(animationName)))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -830,6 +830,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.scheduleTextNode = ImmediateTextNode()
|
self.scheduleTextNode = ImmediateTextNode()
|
||||||
self.scheduleTextNode.isHidden = !self.isScheduling
|
self.scheduleTextNode.isHidden = !self.isScheduling
|
||||||
|
self.scheduleTextNode.isUserInteractionEnabled = false
|
||||||
|
self.scheduleTextNode.textAlignment = .center
|
||||||
|
self.scheduleTextNode.maximumNumberOfLines = 4
|
||||||
|
|
||||||
self.scheduleCancelButton = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x2b2b2f), foregroundColor: .white), height: 52.0, cornerRadius: 10.0)
|
self.scheduleCancelButton = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x2b2b2f), foregroundColor: .white), height: 52.0, cornerRadius: 10.0)
|
||||||
self.scheduleCancelButton.isHidden = !self.isScheduling
|
self.scheduleCancelButton.isHidden = !self.isScheduling
|
||||||
@ -840,6 +843,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.dateFormatter.timeZone = TimeZone.current
|
self.dateFormatter.timeZone = TimeZone.current
|
||||||
|
|
||||||
self.timerNode = VoiceChatTimerNode(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)
|
self.timerNode = VoiceChatTimerNode(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)
|
||||||
|
self.timerNode.isHidden = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -1258,10 +1262,17 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, maxLength: maxBioLength, apply: { bio in
|
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, maxLength: maxBioLength, apply: { bio in
|
||||||
if let strongSelf = self, let bio = bio {
|
if let strongSelf = self, let bio = bio {
|
||||||
|
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||||
let _ = (updateAbout(account: strongSelf.context.account, about: bio)
|
let _ = (updateAbout(account: strongSelf.context.account, about: bio)
|
||||||
|> `catch` { _ -> Signal<Void, NoError> in
|
|> `catch` { _ -> Signal<Void, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}).start()
|
}).start()
|
||||||
|
} else {
|
||||||
|
let _ = (updatePeerTitle(account: strongSelf.context.account, peerId: peer.id, title: bio)
|
||||||
|
|> `catch` { _ -> Signal<Void, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
||||||
}
|
}
|
||||||
@ -1501,6 +1512,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.contentContainer.addSubnode(self.rightBorderNode)
|
self.contentContainer.addSubnode(self.rightBorderNode)
|
||||||
self.contentContainer.addSubnode(self.bottomPanelNode)
|
self.contentContainer.addSubnode(self.bottomPanelNode)
|
||||||
self.contentContainer.addSubnode(self.timerNode)
|
self.contentContainer.addSubnode(self.timerNode)
|
||||||
|
self.contentContainer.addSubnode(self.scheduleTextNode)
|
||||||
|
|
||||||
let invitedPeers: Signal<[Peer], NoError> = self.call.invitedPeers
|
let invitedPeers: Signal<[Peer], NoError> = self.call.invitedPeers
|
||||||
|> mapToSignal { ids -> Signal<[Peer], NoError> in
|
|> mapToSignal { ids -> Signal<[Peer], NoError> in
|
||||||
@ -1700,7 +1712,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.currentContentOffset = offset
|
strongSelf.currentContentOffset = offset
|
||||||
if strongSelf.animation == nil && !strongSelf.animatingExpansion {
|
if !strongSelf.animatingExpansion && !strongSelf.animatingInsertion && strongSelf.panGestureArguments == nil {
|
||||||
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
|
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1994,7 +2006,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.isNoiseSuppressionEnabled ? "Disable Noise Suppression" : "Enable Noise Suppression", textColor: .primary, icon: { theme in
|
/*items.append(.action(ContextMenuActionItem(text: strongSelf.isNoiseSuppressionEnabled ? "Disable Noise Suppression" : "Enable Noise Suppression", textColor: .primary, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
@ -2004,7 +2016,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.call.setIsNoiseSuppressionEnabled(!strongSelf.isNoiseSuppressionEnabled)
|
strongSelf.call.setIsNoiseSuppressionEnabled(!strongSelf.isNoiseSuppressionEnabled)
|
||||||
})))
|
})))*/
|
||||||
|
|
||||||
if let callState = strongSelf.callState, callState.canManageCall {
|
if let callState = strongSelf.callState, callState.canManageCall {
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
|
||||||
@ -2206,7 +2218,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.view.addGestureRecognizer(panRecognizer)
|
self.view.addGestureRecognizer(panRecognizer)
|
||||||
|
|
||||||
if self.isScheduling {
|
if self.isScheduling {
|
||||||
self.setupPickerView()
|
self.setupSchedulePickerView()
|
||||||
self.updateScheduleButtonTitle()
|
self.updateScheduleButtonTitle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2218,22 +2230,27 @@ public final class VoiceChatController: ViewController {
|
|||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: currentDate)
|
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: currentDate)
|
||||||
components.second = 0
|
components.second = 0
|
||||||
let minute = (components.minute ?? 0) % 5
|
|
||||||
|
|
||||||
let next1MinDate = calendar.date(byAdding: .minute, value: 1, to: calendar.date(from: components)!)
|
let roundedDate = calendar.date(from: components)!
|
||||||
let next5MinDate = calendar.date(byAdding: .minute, value: 5 - minute, to: calendar.date(from: components)!)
|
let next1MinDate = calendar.date(byAdding: .minute, value: 1, to: roundedDate)
|
||||||
|
|
||||||
|
components.minute = 0
|
||||||
|
let roundedToHourDate = calendar.date(from: components)!
|
||||||
|
let nextTwoHourDate = calendar.date(byAdding: .hour, value: 2, to: roundedToHourDate)
|
||||||
|
let maxDate = calendar.date(byAdding: .day, value: 7, to: currentDate)
|
||||||
|
|
||||||
if let date = calendar.date(byAdding: .day, value: 365, to: currentDate) {
|
if let date = calendar.date(byAdding: .day, value: 365, to: currentDate) {
|
||||||
self.pickerView?.maximumDate = date
|
self.pickerView?.maximumDate = date
|
||||||
}
|
}
|
||||||
|
|
||||||
if let next1MinDate = next1MinDate, let next5MinDate = next5MinDate {
|
if let next1MinDate = next1MinDate, let nextTwoHourDate = nextTwoHourDate {
|
||||||
self.pickerView?.minimumDate = next1MinDate
|
self.pickerView?.minimumDate = next1MinDate
|
||||||
self.pickerView?.date = next5MinDate
|
self.pickerView?.maximumDate = maxDate
|
||||||
|
self.pickerView?.date = nextTwoHourDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupPickerView() {
|
private func setupSchedulePickerView() {
|
||||||
var currentDate: Date?
|
var currentDate: Date?
|
||||||
if let pickerView = self.pickerView {
|
if let pickerView = self.pickerView {
|
||||||
currentDate = pickerView.date
|
currentDate = pickerView.date
|
||||||
@ -2271,7 +2288,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let calendar = Calendar(identifier: .gregorian)
|
let calendar = Calendar(identifier: .gregorian)
|
||||||
let time = stringForMessageTimestamp(timestamp: Int32(date.timeIntervalSince1970), dateTimeFormat: self.presentationData.dateTimeFormat)
|
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
let timestamp = Int32(date.timeIntervalSince1970)
|
||||||
|
let time = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: self.presentationData.dateTimeFormat)
|
||||||
let buttonTitle: String
|
let buttonTitle: String
|
||||||
if calendar.isDateInToday(date) {
|
if calendar.isDateInToday(date) {
|
||||||
buttonTitle = self.presentationData.strings.ScheduleVoiceChat_ScheduleToday(time).0
|
buttonTitle = self.presentationData.strings.ScheduleVoiceChat_ScheduleToday(time).0
|
||||||
@ -2282,6 +2301,15 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
self.scheduleButtonTitle = buttonTitle
|
self.scheduleButtonTitle = buttonTitle
|
||||||
|
|
||||||
|
let delta = timestamp - currentTimestamp
|
||||||
|
|
||||||
|
var isGroup = true
|
||||||
|
if let peer = self.peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
isGroup = false
|
||||||
|
}
|
||||||
|
let intervalString = timeIntervalString(strings: self.presentationData.strings, value: max(60, delta))
|
||||||
|
self.scheduleTextNode.attributedText = NSAttributedString(string: isGroup ? self.presentationData.strings.ScheduleVoiceChat_GroupText(intervalString).0 : self.presentationData.strings.ScheduleVoiceChat_ChannelText(intervalString).0, font: Font.regular(14.0), textColor: UIColor(rgb: 0x8e8e93))
|
||||||
|
|
||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
@ -2341,16 +2369,22 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.actionButton.titleLabel.layer.animatePosition(from: CGPoint(x: 0.0, y: -26.0), to: CGPoint(), duration: 0.2, additive: true)
|
self.actionButton.titleLabel.layer.animatePosition(from: CGPoint(x: 0.0, y: -26.0), to: CGPoint(), duration: 0.2, additive: true)
|
||||||
|
|
||||||
if let pickerView = self.pickerView {
|
if let pickerView = self.pickerView {
|
||||||
|
self.pickerView = nil
|
||||||
pickerView.alpha = 0.0
|
pickerView.alpha = 0.0
|
||||||
pickerView.layer.animateScale(from: 1.0, to: 0.25, duration: 0.15, removeOnCompletion: false)
|
pickerView.layer.animateScale(from: 1.0, to: 0.25, duration: 0.15, removeOnCompletion: false)
|
||||||
pickerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
pickerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak pickerView] _ in
|
||||||
|
pickerView?.removeFromSuperview()
|
||||||
|
})
|
||||||
pickerView.isUserInteractionEnabled = false
|
pickerView.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.timerNode.alpha = 1.0
|
self.timerNode.isHidden = false
|
||||||
self.timerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.timerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
self.timerNode.animateIn()
|
self.timerNode.animateIn()
|
||||||
|
|
||||||
|
self.scheduleTextNode.alpha = 0.0
|
||||||
|
self.scheduleTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||||
|
|
||||||
self.updateTitle(slide: true, transition: .animated(duration: 0.2, curve: .easeInOut))
|
self.updateTitle(slide: true, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2362,7 +2396,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.listNode.isUserInteractionEnabled = true
|
self.listNode.isUserInteractionEnabled = true
|
||||||
|
|
||||||
self.timerNode.alpha = 0.0
|
self.timerNode.alpha = 0.0
|
||||||
self.timerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
self.timerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||||
|
self?.timerNode.isHidden = true
|
||||||
|
})
|
||||||
|
|
||||||
self.updateTitle(transition: .animated(duration: 0.2, curve: .easeInOut))
|
self.updateTitle(transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
@ -2602,7 +2638,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.schedule()
|
self.schedule()
|
||||||
} else if callState.canManageCall {
|
} else if callState.canManageCall {
|
||||||
self.call.startScheduled()
|
self.call.startScheduled()
|
||||||
self.transitionToCall()
|
|
||||||
} else {
|
} else {
|
||||||
self.call.toggleScheduledSubscription(!callState.subscribedToScheduled)
|
self.call.toggleScheduledSubscription(!callState.subscribedToScheduled)
|
||||||
}
|
}
|
||||||
@ -2678,8 +2713,13 @@ public final class VoiceChatController: ViewController {
|
|||||||
let _ = (self.inviteLinksPromise.get()
|
let _ = (self.inviteLinksPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] inviteLinks in
|
|> deliverOnMainQueue).start(next: { [weak self] inviteLinks in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
if let inviteLinks = inviteLinks {
|
if let inviteLinks = inviteLinks {
|
||||||
self?.presentShare(inviteLinks)
|
strongSelf.presentShare(inviteLinks)
|
||||||
|
} else if let addressName = strongSelf.peer?.addressName, !addressName.isEmpty {
|
||||||
|
strongSelf.presentShare(GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)?voicechat", speakerLink: nil))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -2790,8 +2830,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
|
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
|
||||||
}
|
}
|
||||||
} else if let _ = self.animation {
|
|
||||||
topInset = self.listNode.frame.minY - listTopInset
|
|
||||||
} else if let currentTopInset = self.topInset {
|
} else if let currentTopInset = self.topInset {
|
||||||
topInset = self.isExpanded ? 0.0 : currentTopInset
|
topInset = self.isExpanded ? 0.0 : currentTopInset
|
||||||
} else {
|
} else {
|
||||||
@ -2845,7 +2883,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
var bottomEdge: CGFloat = 0.0
|
var bottomEdge: CGFloat = 0.0
|
||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ListViewItemNode {
|
if let itemNode = itemNode as? ListViewItemNode {
|
||||||
let convertedFrame = self.listNode.view.convert(itemNode.frame, to: self.view)
|
let convertedFrame = self.listNode.view.convert(itemNode.frame, to: self.contentContainer.view)
|
||||||
if convertedFrame.maxY > bottomEdge {
|
if convertedFrame.maxY > bottomEdge {
|
||||||
bottomEdge = convertedFrame.maxY
|
bottomEdge = convertedFrame.maxY
|
||||||
}
|
}
|
||||||
@ -2853,14 +2891,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let listMaxY = listTopInset + listSize.height
|
let listMaxY = listTopInset + listSize.height
|
||||||
if bottomEdge.isZero {
|
let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY)
|
||||||
bottomEdge = listMaxY
|
|
||||||
}
|
|
||||||
|
|
||||||
var bottomOffset: CGFloat = 0.0
|
|
||||||
if bottomEdge < listMaxY && (self.panGestureArguments != nil || self.isExpanded) {
|
|
||||||
bottomOffset = bottomEdge - listMaxY
|
|
||||||
}
|
|
||||||
|
|
||||||
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0))
|
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0))
|
||||||
let previousBottomCornersFrame = self.bottomCornersNode.frame
|
let previousBottomCornersFrame = self.bottomCornersNode.frame
|
||||||
@ -3139,9 +3170,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
topInset = listSize.height
|
topInset = listSize.height
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.animation == nil {
|
|
||||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + topInset), size: listSize))
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + topInset), size: listSize))
|
||||||
}
|
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
||||||
@ -3161,6 +3190,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
transition.updateFrame(node: self.timerNode, frame: timerFrame)
|
transition.updateFrame(node: self.timerNode, frame: timerFrame)
|
||||||
self.timerNode.update(size: timerFrame.size, scheduleTime: self.callState?.scheduleTimestamp, transition: .immediate)
|
self.timerNode.update(size: timerFrame.size, scheduleTime: self.callState?.scheduleTimestamp, transition: .immediate)
|
||||||
|
|
||||||
|
let scheduleTextSize = self.scheduleTextNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
self.scheduleTextNode.frame = CGRect(origin: CGPoint(x: floor((size.width - scheduleTextSize.width) / 2.0), y: layout.size.height - layout.intrinsicInsets.bottom - scheduleTextSize.height - 145.0), size: scheduleTextSize)
|
||||||
|
|
||||||
let centralButtonSide = min(size.width, size.height) - 32.0
|
let centralButtonSide = min(size.width, size.height) - 32.0
|
||||||
let centralButtonSize = CGSize(width: centralButtonSide, height: centralButtonSide)
|
let centralButtonSize = CGSize(width: centralButtonSide, height: centralButtonSide)
|
||||||
let cameraButtonSize = CGSize(width: 36.0, height: 36.0)
|
let cameraButtonSize = CGSize(width: 36.0, height: 36.0)
|
||||||
@ -3188,7 +3220,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
smallButtons = false
|
smallButtons = false
|
||||||
firstButtonFrame = CGRect(origin: CGPoint(x: floor(leftButtonFrame.midX - cameraButtonSize.width / 2.0), y: leftButtonFrame.minY - upperButtonDistance - cameraButtonSize.height), size: cameraButtonSize)
|
firstButtonFrame = CGRect(origin: CGPoint(x: floor(leftButtonFrame.midX - cameraButtonSize.width / 2.0), y: leftButtonFrame.minY - upperButtonDistance - cameraButtonSize.height), size: cameraButtonSize)
|
||||||
secondButtonFrame = leftButtonFrame
|
secondButtonFrame = leftButtonFrame
|
||||||
thirdButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((self.effectiveBottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
thirdButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floor((self.effectiveBottomAreaHeight - centralButtonSize.height) / 2.0) - 3.0), size: centralButtonSize)
|
||||||
forthButtonFrame = rightButtonFrame
|
forthButtonFrame = rightButtonFrame
|
||||||
case let .fullscreen(controlsHidden):
|
case let .fullscreen(controlsHidden):
|
||||||
smallButtons = true
|
smallButtons = true
|
||||||
@ -3398,10 +3430,13 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.enqueuedTransitions.remove(at: 0)
|
self.enqueuedTransitions.remove(at: 0)
|
||||||
|
|
||||||
if self.callState?.scheduleTimestamp != nil && self.listNode.alpha > 0.0 {
|
if self.callState?.scheduleTimestamp != nil && self.listNode.alpha > 0.0 {
|
||||||
|
self.timerNode.isHidden = false
|
||||||
self.listNode.alpha = 0.0
|
self.listNode.alpha = 0.0
|
||||||
self.listNode.isUserInteractionEnabled = false
|
self.listNode.isUserInteractionEnabled = false
|
||||||
self.backgroundNode.backgroundColor = panelBackgroundColor
|
self.backgroundNode.backgroundColor = panelBackgroundColor
|
||||||
self.updateIsFullscreen(false)
|
self.updateIsFullscreen(false)
|
||||||
|
} else if self.callState?.scheduleTimestamp == nil && !self.isScheduling && self.listNode.alpha == 0.0 {
|
||||||
|
self.transitionToCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = ListViewDeleteAndInsertOptions()
|
var options = ListViewDeleteAndInsertOptions()
|
||||||
@ -3412,7 +3447,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if transition.crossFade {
|
if transition.crossFade {
|
||||||
options.insert(.AnimateCrossfade)
|
options.insert(.AnimateCrossfade)
|
||||||
}
|
}
|
||||||
if transition.animated && self.animation == nil {
|
if transition.animated {
|
||||||
options.insert(.AnimateInsertion)
|
options.insert(.AnimateInsertion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3443,11 +3478,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let listTopInset = layoutTopInset + 63.0
|
let listTopInset = layoutTopInset + 63.0
|
||||||
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
|
|
||||||
if self.isScheduling || self.callState?.scheduleTimestamp != nil {
|
|
||||||
self.topInset = listSize.height - 46.0 - floor(56.0 * 3.5)
|
self.topInset = listSize.height - 46.0 - floor(56.0 * 3.5)
|
||||||
} else {
|
|
||||||
self.topInset = max(0.0, max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetY = listTopInset + (self.topInset ?? listSize.height)
|
let targetY = listTopInset + (self.topInset ?? listSize.height)
|
||||||
|
|
||||||
@ -3455,24 +3486,20 @@ public final class VoiceChatController: ViewController {
|
|||||||
var frame = self.listNode.frame
|
var frame = self.listNode.frame
|
||||||
frame.origin.y = targetY
|
frame.origin.y = targetY
|
||||||
self.listNode.frame = frame
|
self.listNode.frame = frame
|
||||||
} else if !self.isExpanded {
|
|
||||||
if self.listNode.frame.minY != targetY && !self.animatingExpansion && self.panGestureArguments == nil {
|
|
||||||
self.animation = ListViewAnimation(from: self.listNode.frame.minY, to: targetY, duration: 0.4, curve: listViewAnimationCurveSystem, beginAt: CACurrentMediaTime(), update: { [weak self] _, currentValue in
|
|
||||||
if let strongSelf = self {
|
|
||||||
var frame = strongSelf.listNode.frame
|
|
||||||
frame.origin.y = currentValue
|
|
||||||
strongSelf.listNode.frame = frame
|
|
||||||
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .immediate)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if transition.animated {
|
||||||
|
self.animatingInsertion = true
|
||||||
|
}
|
||||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strongSelf.animatingInsertion {
|
||||||
|
strongSelf.updateFloatingHeaderOffset(offset: self?.currentContentOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
|
strongSelf.animatingInsertion = false
|
||||||
|
}
|
||||||
if !strongSelf.didSetContentsReady {
|
if !strongSelf.didSetContentsReady {
|
||||||
strongSelf.didSetContentsReady = true
|
strongSelf.didSetContentsReady = true
|
||||||
strongSelf.controller?.contentsReady.set(true)
|
strongSelf.controller?.contentsReady.set(true)
|
||||||
@ -3480,39 +3507,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private var animator: ConstantDisplayLinkAnimator?
|
|
||||||
private var animation: ListViewAnimation?
|
|
||||||
private func updateAnimation() {
|
|
||||||
var animate = false
|
|
||||||
let timestamp = CACurrentMediaTime()
|
|
||||||
|
|
||||||
if let animation = self.animation {
|
|
||||||
animation.applyAt(timestamp)
|
|
||||||
|
|
||||||
if animation.completeAt(timestamp) {
|
|
||||||
self.animation = nil
|
|
||||||
} else {
|
|
||||||
animate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if animate {
|
|
||||||
let animator: ConstantDisplayLinkAnimator
|
|
||||||
if let current = self.animator {
|
|
||||||
animator = current
|
|
||||||
} else {
|
|
||||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
|
||||||
self?.updateAnimation()
|
|
||||||
})
|
|
||||||
self.animator = animator
|
|
||||||
}
|
|
||||||
animator.isPaused = false
|
|
||||||
} else {
|
|
||||||
self.animator?.isPaused = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: ([GroupCallParticipantsContext.Participant], String?), invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
|
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: ([GroupCallParticipantsContext.Participant], String?), invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
|
||||||
var disableAnimation = false
|
var disableAnimation = false
|
||||||
if self.currentCallMembers?.1 != callMembers.1 {
|
if self.currentCallMembers?.1 != callMembers.1 {
|
||||||
@ -3689,6 +3683,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.itemInteraction?.isExpanded = self.isExpanded
|
self.itemInteraction?.isExpanded = self.isExpanded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var animatingInsertion = false
|
||||||
private var animatingExpansion = false
|
private var animatingExpansion = false
|
||||||
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
|
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
|
||||||
|
|
||||||
@ -3914,8 +3910,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: strongSelf.presentationData.strings.VoiceChat_EditTitleTitle, text: strongSelf.presentationData.strings.VoiceChat_EditTitleText, placeholder: chatPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), value: strongSelf.callState?.title, maxLength: 40, apply: { title in
|
let initialTitle = strongSelf.callState?.title ?? ""
|
||||||
if let strongSelf = self, let title = title {
|
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: strongSelf.presentationData.strings.VoiceChat_EditTitleTitle, text: strongSelf.presentationData.strings.VoiceChat_EditTitleText, placeholder: chatPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { title in
|
||||||
|
if let strongSelf = self, let title = title, title != initialTitle {
|
||||||
strongSelf.call.updateTitle(title)
|
strongSelf.call.updateTitle(title)
|
||||||
|
|
||||||
strongSelf.presentUndoOverlay(content: .voiceChatFlag(text: title.isEmpty ? strongSelf.presentationData.strings.VoiceChat_EditTitleRemoveSuccess : strongSelf.presentationData.strings.VoiceChat_EditTitleSuccess(title).0), action: { _ in return false })
|
strongSelf.presentUndoOverlay(content: .voiceChatFlag(text: title.isEmpty ? strongSelf.presentationData.strings.VoiceChat_EditTitleRemoveSuccess : strongSelf.presentationData.strings.VoiceChat_EditTitleSuccess(title).0), action: { _ in return false })
|
||||||
|
|||||||
@ -158,12 +158,12 @@ final class VoiceChatMicrophoneNode: ASDisplayNode {
|
|||||||
|
|
||||||
context.setFillColor(parameters.color.cgColor)
|
context.setFillColor(parameters.color.cgColor)
|
||||||
|
|
||||||
var clearLineWidth: CGFloat = 4.0
|
var clearLineWidth: CGFloat = 2.0
|
||||||
var lineWidth: CGFloat = 1.0 + UIScreenPixel
|
var lineWidth: CGFloat = 1.0 + UIScreenPixel
|
||||||
if bounds.size.width > 36.0 {
|
if bounds.size.width > 36.0 {
|
||||||
context.scaleBy(x: 2.0, y: 2.0)
|
context.scaleBy(x: 2.0, y: 2.0)
|
||||||
} else if bounds.size.width < 30.0 {
|
} else if bounds.size.width < 30.0 {
|
||||||
clearLineWidth = 3.0
|
clearLineWidth = 2.0
|
||||||
lineWidth = 1.0
|
lineWidth = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,18 +207,19 @@ final class VoiceChatMicrophoneNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parameters.reverse {
|
if parameters.reverse {
|
||||||
startPoint = CGPoint(x: origin.x + length * (1.0 - parameters.transition), y: origin.y + length * (1.0 - parameters.transition))
|
startPoint = CGPoint(x: origin.x + length * (1.0 - parameters.transition), y: origin.y + length * (1.0 - parameters.transition)).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
|
||||||
endPoint = CGPoint(x: origin.x + length, y: origin.y + length)
|
endPoint = CGPoint(x: origin.x + length, y: origin.y + length).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
|
||||||
} else {
|
} else {
|
||||||
startPoint = origin
|
startPoint = origin.offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
|
||||||
endPoint = CGPoint(x: origin.x + length * parameters.transition, y: origin.y + length * parameters.transition)
|
endPoint = CGPoint(x: origin.x + length * parameters.transition, y: origin.y + length * parameters.transition).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
context.setBlendMode(.clear)
|
context.setBlendMode(.clear)
|
||||||
context.setLineWidth(clearLineWidth)
|
context.setLineWidth(clearLineWidth)
|
||||||
|
|
||||||
context.move(to: startPoint)
|
context.move(to: startPoint.offsetBy(dx: 0.0, dy: 1.0 + UIScreenPixel))
|
||||||
context.addLine(to: endPoint)
|
context.addLine(to: endPoint.offsetBy(dx: 0.0, dy: 1.0 + UIScreenPixel))
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
|
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
|
|||||||
@ -169,7 +169,7 @@ public final class VoiceChatOverlayController: ViewController {
|
|||||||
|
|
||||||
if reclaim {
|
if reclaim {
|
||||||
self.dismissed = true
|
self.dismissed = true
|
||||||
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 205.0 / 2.0)
|
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 205.0 / 2.0 - 2.0)
|
||||||
if self.isSlidOffscreen {
|
if self.isSlidOffscreen {
|
||||||
self.isSlidOffscreen = false
|
self.isSlidOffscreen = false
|
||||||
self.isButtonHidden = true
|
self.isButtonHidden = true
|
||||||
|
|||||||
@ -133,10 +133,8 @@ final class VoiceChatTimerNode: ASDisplayNode {
|
|||||||
let timerText: String
|
let timerText: String
|
||||||
if elapsedTime >= 86400 {
|
if elapsedTime >= 86400 {
|
||||||
timerText = timeIntervalString(strings: self.strings, value: elapsedTime)
|
timerText = timeIntervalString(strings: self.strings, value: elapsedTime)
|
||||||
} else if elapsedTime < 0 {
|
|
||||||
timerText = "\(textForTimeout(value: abs(elapsedTime)))"
|
|
||||||
} else {
|
} else {
|
||||||
timerText = textForTimeout(value: elapsedTime)
|
timerText = textForTimeout(value: abs(elapsedTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.updateTimer == nil {
|
if self.updateTimer == nil {
|
||||||
|
|||||||
@ -551,6 +551,10 @@ private final class VoiceChatUserNameEditAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.updateTheme(theme)
|
self.updateTheme(theme)
|
||||||
|
|
||||||
|
self.firstNameInputFieldNode.complete = { [weak self] in
|
||||||
|
self?.lastNameInputFieldNode.activateInput()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|||||||
@ -393,11 +393,12 @@ public func sendBotPaymentForm(account: Account, messageId: MessageId, formId: I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct BotPaymentReceipt : Equatable {
|
public struct BotPaymentReceipt {
|
||||||
public let invoice: BotPaymentInvoice
|
public let invoice: BotPaymentInvoice
|
||||||
public let info: BotPaymentRequestedInfo?
|
public let info: BotPaymentRequestedInfo?
|
||||||
public let shippingOption: BotPaymentShippingOption?
|
public let shippingOption: BotPaymentShippingOption?
|
||||||
public let credentialsTitle: String
|
public let credentialsTitle: String
|
||||||
|
public let invoiceMedia: TelegramMediaInvoice
|
||||||
public let tipAmount: Int64?
|
public let tipAmount: Int64?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,15 +420,56 @@ public func requestBotPaymentReceipt(account: Account, messageId: MessageId) ->
|
|||||||
|> mapError { _ -> RequestBotPaymentReceiptError in
|
|> mapError { _ -> RequestBotPaymentReceiptError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> map { result -> BotPaymentReceipt in
|
|> mapToSignal { result -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> in
|
||||||
|
return account.postbox.transaction { transaction -> BotPaymentReceipt in
|
||||||
switch result {
|
switch result {
|
||||||
case let .paymentReceipt(flags, date, botId, providerId, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users):
|
case let .paymentReceipt(flags, date, botId, providerId, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users):
|
||||||
|
var peers: [Peer] = []
|
||||||
|
for user in users {
|
||||||
|
peers.append(TelegramUser(user: user))
|
||||||
|
}
|
||||||
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated in return updated })
|
||||||
|
|
||||||
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||||
let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init)
|
let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init)
|
||||||
let shippingOption = shipping.flatMap(BotPaymentShippingOption.init)
|
let shippingOption = shipping.flatMap(BotPaymentShippingOption.init)
|
||||||
return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle, tipAmount: tipAmount)
|
|
||||||
|
/*let fields = BotPaymentInvoiceFields()
|
||||||
|
|
||||||
|
let form = BotPaymentForm(
|
||||||
|
id: 0,
|
||||||
|
canSaveCredentials: false,
|
||||||
|
passwordMissing: false,
|
||||||
|
invoice: BotPaymentInvoice(
|
||||||
|
isTest: false,
|
||||||
|
requestedFields: fields,
|
||||||
|
currency: currency,
|
||||||
|
prices: [],
|
||||||
|
tip: nil
|
||||||
|
),
|
||||||
|
providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt32Value(providerId)),
|
||||||
|
url: "",
|
||||||
|
nativeProvider: nil,
|
||||||
|
savedInfo: nil,
|
||||||
|
savedCredentials: nil
|
||||||
|
)*/
|
||||||
|
|
||||||
|
let invoiceMedia = TelegramMediaInvoice(
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
photo: photo.flatMap(TelegramMediaWebFile.init),
|
||||||
|
receiptMessageId: nil,
|
||||||
|
currency: currency,
|
||||||
|
totalAmount: totalAmount,
|
||||||
|
startParam: "",
|
||||||
|
flags: []
|
||||||
|
)
|
||||||
|
|
||||||
|
return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle, invoiceMedia: invoiceMedia, tipAmount: tipAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|> castError(RequestBotPaymentReceiptError.self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -184,6 +184,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
|
|||||||
public enum CreateGroupCallError {
|
public enum CreateGroupCallError {
|
||||||
case generic
|
case generic
|
||||||
case anonymousNotAllowed
|
case anonymousNotAllowed
|
||||||
|
case scheduledTooLate
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createGroupCall(account: Account, peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal<GroupCallInfo, CreateGroupCallError> {
|
public func createGroupCall(account: Account, peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal<GroupCallInfo, CreateGroupCallError> {
|
||||||
@ -208,6 +209,8 @@ public func createGroupCall(account: Account, peerId: PeerId, title: String?, sc
|
|||||||
|> mapError { error -> CreateGroupCallError in
|
|> mapError { error -> CreateGroupCallError in
|
||||||
if error.errorDescription == "ANONYMOUS_CALLS_DISABLED" {
|
if error.errorDescription == "ANONYMOUS_CALLS_DISABLED" {
|
||||||
return .anonymousNotAllowed
|
return .anonymousNotAllowed
|
||||||
|
} else if error.errorDescription == "SCHEDULE_DATE_TOO_LATE" {
|
||||||
|
return .scheduledTooLate
|
||||||
}
|
}
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
@ -1005,6 +1008,11 @@ public final class GroupCallParticipantsContext {
|
|||||||
public struct DefaultParticipantsAreMuted: Equatable {
|
public struct DefaultParticipantsAreMuted: Equatable {
|
||||||
public var isMuted: Bool
|
public var isMuted: Bool
|
||||||
public var canChange: Bool
|
public var canChange: Bool
|
||||||
|
|
||||||
|
public init(isMuted: Bool, canChange: Bool) {
|
||||||
|
self.isMuted = isMuted
|
||||||
|
self.canChange = canChange
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var participants: [Participant]
|
public var participants: [Participant]
|
||||||
@ -1037,6 +1045,34 @@ public final class GroupCallParticipantsContext {
|
|||||||
|
|
||||||
self.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: self.sortAscending) })
|
self.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: self.sortAscending) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
participants: [Participant],
|
||||||
|
nextParticipantsFetchOffset: String?,
|
||||||
|
adminIds: Set<PeerId>,
|
||||||
|
isCreator: Bool,
|
||||||
|
defaultParticipantsAreMuted: DefaultParticipantsAreMuted,
|
||||||
|
sortAscending: Bool,
|
||||||
|
recordingStartTimestamp: Int32?,
|
||||||
|
title: String?,
|
||||||
|
scheduleTimestamp: Int32?,
|
||||||
|
subscribedToScheduled: Bool,
|
||||||
|
totalCount: Int,
|
||||||
|
version: Int32
|
||||||
|
) {
|
||||||
|
self.participants = participants
|
||||||
|
self.nextParticipantsFetchOffset = nextParticipantsFetchOffset
|
||||||
|
self.adminIds = adminIds
|
||||||
|
self.isCreator = isCreator
|
||||||
|
self.defaultParticipantsAreMuted = defaultParticipantsAreMuted
|
||||||
|
self.sortAscending = sortAscending
|
||||||
|
self.recordingStartTimestamp = recordingStartTimestamp
|
||||||
|
self.title = title
|
||||||
|
self.scheduleTimestamp = scheduleTimestamp
|
||||||
|
self.subscribedToScheduled = subscribedToScheduled
|
||||||
|
self.totalCount = totalCount
|
||||||
|
self.version = version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct OverlayState: Equatable {
|
private struct OverlayState: Equatable {
|
||||||
|
|||||||
@ -359,7 +359,13 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
|
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
|
||||||
inputClearButtonColor: UIColor(rgb: 0x8b9197),
|
inputClearButtonColor: UIColor(rgb: 0x8b9197),
|
||||||
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)),
|
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)),
|
||||||
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f))
|
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f)),
|
||||||
|
paymentOption: PresentationThemeList.PaymentOption(
|
||||||
|
inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.3),
|
||||||
|
inactiveForegroundColor: UIColor(rgb: 0x00A650),
|
||||||
|
activeFillColor: UIColor(rgb: 0x00A650),
|
||||||
|
activeForegroundColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let chatList = PresentationThemeChatList(
|
let chatList = PresentationThemeChatList(
|
||||||
|
|||||||
@ -612,7 +612,13 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
|
|||||||
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
||||||
inputClearButtonColor: mainSecondaryColor,
|
inputClearButtonColor: mainSecondaryColor,
|
||||||
itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)),
|
itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)),
|
||||||
itemInputField: PresentationInputFieldTheme(backgroundColor: mainInputColor, strokeColor: mainInputColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor)
|
itemInputField: PresentationInputFieldTheme(backgroundColor: mainInputColor, strokeColor: mainInputColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor),
|
||||||
|
paymentOption: PresentationThemeList.PaymentOption(
|
||||||
|
inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.3),
|
||||||
|
inactiveForegroundColor: UIColor(rgb: 0x00A650),
|
||||||
|
activeFillColor: UIColor(rgb: 0x00A650),
|
||||||
|
activeForegroundColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let chatList = PresentationThemeChatList(
|
let chatList = PresentationThemeChatList(
|
||||||
|
|||||||
@ -448,7 +448,13 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
|
|||||||
pageIndicatorInactiveColor: UIColor(rgb: 0xe3e3e7),
|
pageIndicatorInactiveColor: UIColor(rgb: 0xe3e3e7),
|
||||||
inputClearButtonColor: UIColor(rgb: 0xcccccc),
|
inputClearButtonColor: UIColor(rgb: 0xcccccc),
|
||||||
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0x007ee5), color2: UIColor(rgb: 0xc8c7cc), color3: UIColor(rgb: 0xf2f1f7)),
|
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0x007ee5), color2: UIColor(rgb: 0xc8c7cc), color3: UIColor(rgb: 0xf2f1f7)),
|
||||||
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0xf2f2f7), strokeColor: UIColor(rgb: 0xf2f2f7), placeholderColor: UIColor(rgb: 0xb6b6bb), primaryColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0xb6b6bb))
|
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0xf2f2f7), strokeColor: UIColor(rgb: 0xf2f2f7), placeholderColor: UIColor(rgb: 0xb6b6bb), primaryColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0xb6b6bb)),
|
||||||
|
paymentOption: PresentationThemeList.PaymentOption(
|
||||||
|
inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.1),
|
||||||
|
inactiveForegroundColor: UIColor(rgb: 0x00A650),
|
||||||
|
activeFillColor: UIColor(rgb: 0x00A650),
|
||||||
|
activeForegroundColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let chatList = PresentationThemeChatList(
|
let chatList = PresentationThemeChatList(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -406,6 +406,25 @@ public final class PresentationInputFieldTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class PresentationThemeList {
|
public final class PresentationThemeList {
|
||||||
|
public final class PaymentOption {
|
||||||
|
public let inactiveFillColor: UIColor
|
||||||
|
public let inactiveForegroundColor: UIColor
|
||||||
|
public let activeFillColor: UIColor
|
||||||
|
public let activeForegroundColor: UIColor
|
||||||
|
|
||||||
|
public init(
|
||||||
|
inactiveFillColor: UIColor,
|
||||||
|
inactiveForegroundColor: UIColor,
|
||||||
|
activeFillColor: UIColor,
|
||||||
|
activeForegroundColor: UIColor
|
||||||
|
) {
|
||||||
|
self.inactiveFillColor = inactiveFillColor
|
||||||
|
self.inactiveForegroundColor = inactiveForegroundColor
|
||||||
|
self.activeFillColor = activeFillColor
|
||||||
|
self.activeForegroundColor = activeForegroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let blocksBackgroundColor: UIColor
|
public let blocksBackgroundColor: UIColor
|
||||||
public let plainBackgroundColor: UIColor
|
public let plainBackgroundColor: UIColor
|
||||||
public let itemPrimaryTextColor: UIColor
|
public let itemPrimaryTextColor: UIColor
|
||||||
@ -437,8 +456,42 @@ public final class PresentationThemeList {
|
|||||||
public let inputClearButtonColor: UIColor
|
public let inputClearButtonColor: UIColor
|
||||||
public let itemBarChart: PresentationThemeItemBarChart
|
public let itemBarChart: PresentationThemeItemBarChart
|
||||||
public let itemInputField: PresentationInputFieldTheme
|
public let itemInputField: PresentationInputFieldTheme
|
||||||
|
public let paymentOption: PaymentOption
|
||||||
|
|
||||||
public init(blocksBackgroundColor: UIColor, plainBackgroundColor: UIColor, itemPrimaryTextColor: UIColor, itemSecondaryTextColor: UIColor, itemDisabledTextColor: UIColor, itemAccentColor: UIColor, itemHighlightedColor: UIColor, itemDestructiveColor: UIColor, itemPlaceholderTextColor: UIColor, itemBlocksBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemBlocksSeparatorColor: UIColor, itemPlainSeparatorColor: UIColor, disclosureArrowColor: UIColor, sectionHeaderTextColor: UIColor, freeTextColor: UIColor, freeTextErrorColor: UIColor, freeTextSuccessColor: UIColor, freeMonoIconColor: UIColor, itemSwitchColors: PresentationThemeSwitch, itemDisclosureActions: PresentationThemeItemDisclosureActions, itemCheckColors: PresentationThemeFillStrokeForeground, controlSecondaryColor: UIColor, freeInputField: PresentationInputFieldTheme, freePlainInputField: PresentationInputFieldTheme, mediaPlaceholderColor: UIColor, scrollIndicatorColor: UIColor, pageIndicatorInactiveColor: UIColor, inputClearButtonColor: UIColor, itemBarChart: PresentationThemeItemBarChart, itemInputField: PresentationInputFieldTheme) {
|
public init(
|
||||||
|
blocksBackgroundColor: UIColor,
|
||||||
|
plainBackgroundColor: UIColor,
|
||||||
|
itemPrimaryTextColor: UIColor,
|
||||||
|
itemSecondaryTextColor: UIColor,
|
||||||
|
itemDisabledTextColor: UIColor,
|
||||||
|
itemAccentColor: UIColor,
|
||||||
|
itemHighlightedColor: UIColor,
|
||||||
|
itemDestructiveColor: UIColor,
|
||||||
|
itemPlaceholderTextColor: UIColor,
|
||||||
|
itemBlocksBackgroundColor: UIColor,
|
||||||
|
itemHighlightedBackgroundColor: UIColor,
|
||||||
|
itemBlocksSeparatorColor: UIColor,
|
||||||
|
itemPlainSeparatorColor: UIColor,
|
||||||
|
disclosureArrowColor: UIColor,
|
||||||
|
sectionHeaderTextColor: UIColor,
|
||||||
|
freeTextColor: UIColor,
|
||||||
|
freeTextErrorColor: UIColor,
|
||||||
|
freeTextSuccessColor: UIColor,
|
||||||
|
freeMonoIconColor: UIColor,
|
||||||
|
itemSwitchColors: PresentationThemeSwitch,
|
||||||
|
itemDisclosureActions: PresentationThemeItemDisclosureActions,
|
||||||
|
itemCheckColors: PresentationThemeFillStrokeForeground,
|
||||||
|
controlSecondaryColor: UIColor,
|
||||||
|
freeInputField: PresentationInputFieldTheme,
|
||||||
|
freePlainInputField: PresentationInputFieldTheme,
|
||||||
|
mediaPlaceholderColor: UIColor,
|
||||||
|
scrollIndicatorColor: UIColor,
|
||||||
|
pageIndicatorInactiveColor: UIColor,
|
||||||
|
inputClearButtonColor: UIColor,
|
||||||
|
itemBarChart: PresentationThemeItemBarChart,
|
||||||
|
itemInputField: PresentationInputFieldTheme,
|
||||||
|
paymentOption: PaymentOption
|
||||||
|
) {
|
||||||
self.blocksBackgroundColor = blocksBackgroundColor
|
self.blocksBackgroundColor = blocksBackgroundColor
|
||||||
self.plainBackgroundColor = plainBackgroundColor
|
self.plainBackgroundColor = plainBackgroundColor
|
||||||
self.itemPrimaryTextColor = itemPrimaryTextColor
|
self.itemPrimaryTextColor = itemPrimaryTextColor
|
||||||
@ -470,10 +523,11 @@ public final class PresentationThemeList {
|
|||||||
self.inputClearButtonColor = inputClearButtonColor
|
self.inputClearButtonColor = inputClearButtonColor
|
||||||
self.itemBarChart = itemBarChart
|
self.itemBarChart = itemBarChart
|
||||||
self.itemInputField = itemInputField
|
self.itemInputField = itemInputField
|
||||||
|
self.paymentOption = paymentOption
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdated(blocksBackgroundColor: UIColor? = nil, plainBackgroundColor: UIColor? = nil, itemPrimaryTextColor: UIColor? = nil, itemSecondaryTextColor: UIColor? = nil, itemDisabledTextColor: UIColor? = nil, itemAccentColor: UIColor? = nil, itemHighlightedColor: UIColor? = nil, itemDestructiveColor: UIColor? = nil, itemPlaceholderTextColor: UIColor? = nil, itemBlocksBackgroundColor: UIColor? = nil, itemHighlightedBackgroundColor: UIColor? = nil, itemBlocksSeparatorColor: UIColor? = nil, itemPlainSeparatorColor: UIColor? = nil, disclosureArrowColor: UIColor? = nil, sectionHeaderTextColor: UIColor? = nil, freeTextColor: UIColor? = nil, freeTextErrorColor: UIColor? = nil, freeTextSuccessColor: UIColor? = nil, freeMonoIconColor: UIColor? = nil, itemSwitchColors: PresentationThemeSwitch? = nil, itemDisclosureActions: PresentationThemeItemDisclosureActions? = nil, itemCheckColors: PresentationThemeFillStrokeForeground? = nil, controlSecondaryColor: UIColor? = nil, freeInputField: PresentationInputFieldTheme? = nil, freePlainInputField: PresentationInputFieldTheme? = nil, mediaPlaceholderColor: UIColor? = nil, scrollIndicatorColor: UIColor? = nil, pageIndicatorInactiveColor: UIColor? = nil, inputClearButtonColor: UIColor? = nil, itemBarChart: PresentationThemeItemBarChart? = nil, itemInputField: PresentationInputFieldTheme? = nil) -> PresentationThemeList {
|
public func withUpdated(blocksBackgroundColor: UIColor? = nil, plainBackgroundColor: UIColor? = nil, itemPrimaryTextColor: UIColor? = nil, itemSecondaryTextColor: UIColor? = nil, itemDisabledTextColor: UIColor? = nil, itemAccentColor: UIColor? = nil, itemHighlightedColor: UIColor? = nil, itemDestructiveColor: UIColor? = nil, itemPlaceholderTextColor: UIColor? = nil, itemBlocksBackgroundColor: UIColor? = nil, itemHighlightedBackgroundColor: UIColor? = nil, itemBlocksSeparatorColor: UIColor? = nil, itemPlainSeparatorColor: UIColor? = nil, disclosureArrowColor: UIColor? = nil, sectionHeaderTextColor: UIColor? = nil, freeTextColor: UIColor? = nil, freeTextErrorColor: UIColor? = nil, freeTextSuccessColor: UIColor? = nil, freeMonoIconColor: UIColor? = nil, itemSwitchColors: PresentationThemeSwitch? = nil, itemDisclosureActions: PresentationThemeItemDisclosureActions? = nil, itemCheckColors: PresentationThemeFillStrokeForeground? = nil, controlSecondaryColor: UIColor? = nil, freeInputField: PresentationInputFieldTheme? = nil, freePlainInputField: PresentationInputFieldTheme? = nil, mediaPlaceholderColor: UIColor? = nil, scrollIndicatorColor: UIColor? = nil, pageIndicatorInactiveColor: UIColor? = nil, inputClearButtonColor: UIColor? = nil, itemBarChart: PresentationThemeItemBarChart? = nil, itemInputField: PresentationInputFieldTheme? = nil, paymentOption: PaymentOption? = nil) -> PresentationThemeList {
|
||||||
return PresentationThemeList(blocksBackgroundColor: blocksBackgroundColor ?? self.blocksBackgroundColor, plainBackgroundColor: plainBackgroundColor ?? self.plainBackgroundColor, itemPrimaryTextColor: itemPrimaryTextColor ?? self.itemPrimaryTextColor, itemSecondaryTextColor: itemSecondaryTextColor ?? self.itemSecondaryTextColor, itemDisabledTextColor: itemDisabledTextColor ?? self.itemDisabledTextColor, itemAccentColor: itemAccentColor ?? self.itemAccentColor, itemHighlightedColor: itemHighlightedColor ?? self.itemHighlightedColor, itemDestructiveColor: itemDestructiveColor ?? self.itemDestructiveColor, itemPlaceholderTextColor: itemPlaceholderTextColor ?? self.itemPlaceholderTextColor, itemBlocksBackgroundColor: itemBlocksBackgroundColor ?? self.itemBlocksBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, itemBlocksSeparatorColor: itemBlocksSeparatorColor ?? self.itemBlocksSeparatorColor, itemPlainSeparatorColor: itemPlainSeparatorColor ?? self.itemPlainSeparatorColor, disclosureArrowColor: disclosureArrowColor ?? self.disclosureArrowColor, sectionHeaderTextColor: sectionHeaderTextColor ?? self.sectionHeaderTextColor, freeTextColor: freeTextColor ?? self.freeTextColor, freeTextErrorColor: freeTextErrorColor ?? self.freeTextErrorColor, freeTextSuccessColor: freeTextSuccessColor ?? self.freeTextSuccessColor, freeMonoIconColor: freeMonoIconColor ?? self.freeMonoIconColor, itemSwitchColors: itemSwitchColors ?? self.itemSwitchColors, itemDisclosureActions: itemDisclosureActions ?? self.itemDisclosureActions, itemCheckColors: itemCheckColors ?? self.itemCheckColors, controlSecondaryColor: controlSecondaryColor ?? self.controlSecondaryColor, freeInputField: freeInputField ?? self.freeInputField, freePlainInputField: freePlainInputField ?? self.freePlainInputField, mediaPlaceholderColor: mediaPlaceholderColor ?? self.mediaPlaceholderColor, scrollIndicatorColor: scrollIndicatorColor ?? self.scrollIndicatorColor, pageIndicatorInactiveColor: pageIndicatorInactiveColor ?? self.pageIndicatorInactiveColor, inputClearButtonColor: inputClearButtonColor ?? self.inputClearButtonColor, itemBarChart: itemBarChart ?? self.itemBarChart, itemInputField: itemInputField ?? self.itemInputField)
|
return PresentationThemeList(blocksBackgroundColor: blocksBackgroundColor ?? self.blocksBackgroundColor, plainBackgroundColor: plainBackgroundColor ?? self.plainBackgroundColor, itemPrimaryTextColor: itemPrimaryTextColor ?? self.itemPrimaryTextColor, itemSecondaryTextColor: itemSecondaryTextColor ?? self.itemSecondaryTextColor, itemDisabledTextColor: itemDisabledTextColor ?? self.itemDisabledTextColor, itemAccentColor: itemAccentColor ?? self.itemAccentColor, itemHighlightedColor: itemHighlightedColor ?? self.itemHighlightedColor, itemDestructiveColor: itemDestructiveColor ?? self.itemDestructiveColor, itemPlaceholderTextColor: itemPlaceholderTextColor ?? self.itemPlaceholderTextColor, itemBlocksBackgroundColor: itemBlocksBackgroundColor ?? self.itemBlocksBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, itemBlocksSeparatorColor: itemBlocksSeparatorColor ?? self.itemBlocksSeparatorColor, itemPlainSeparatorColor: itemPlainSeparatorColor ?? self.itemPlainSeparatorColor, disclosureArrowColor: disclosureArrowColor ?? self.disclosureArrowColor, sectionHeaderTextColor: sectionHeaderTextColor ?? self.sectionHeaderTextColor, freeTextColor: freeTextColor ?? self.freeTextColor, freeTextErrorColor: freeTextErrorColor ?? self.freeTextErrorColor, freeTextSuccessColor: freeTextSuccessColor ?? self.freeTextSuccessColor, freeMonoIconColor: freeMonoIconColor ?? self.freeMonoIconColor, itemSwitchColors: itemSwitchColors ?? self.itemSwitchColors, itemDisclosureActions: itemDisclosureActions ?? self.itemDisclosureActions, itemCheckColors: itemCheckColors ?? self.itemCheckColors, controlSecondaryColor: controlSecondaryColor ?? self.controlSecondaryColor, freeInputField: freeInputField ?? self.freeInputField, freePlainInputField: freePlainInputField ?? self.freePlainInputField, mediaPlaceholderColor: mediaPlaceholderColor ?? self.mediaPlaceholderColor, scrollIndicatorColor: scrollIndicatorColor ?? self.scrollIndicatorColor, pageIndicatorInactiveColor: pageIndicatorInactiveColor ?? self.pageIndicatorInactiveColor, inputClearButtonColor: inputClearButtonColor ?? self.inputClearButtonColor, itemBarChart: itemBarChart ?? self.itemBarChart, itemInputField: itemInputField ?? self.itemInputField, paymentOption: paymentOption ?? self.paymentOption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -745,6 +745,33 @@ extension PresentationInputFieldTheme: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension PresentationThemeList.PaymentOption: Codable {
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case inactiveFill
|
||||||
|
case inactiveForeground
|
||||||
|
case activeFill
|
||||||
|
case activeForeground
|
||||||
|
}
|
||||||
|
|
||||||
|
public convenience init(from decoder: Decoder) throws {
|
||||||
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
self.init(
|
||||||
|
inactiveFillColor: try decodeColor(values, .inactiveFill),
|
||||||
|
inactiveForegroundColor: try decodeColor(values, .inactiveForeground),
|
||||||
|
activeFillColor: try decodeColor(values, .activeFill),
|
||||||
|
activeForegroundColor: try decodeColor(values, .activeForeground)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var values = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try encodeColor(&values, self.activeFillColor, .inactiveFill)
|
||||||
|
try encodeColor(&values, self.activeForegroundColor, .inactiveForeground)
|
||||||
|
try encodeColor(&values, self.activeFillColor, .activeFill)
|
||||||
|
try encodeColor(&values, self.activeForegroundColor, .activeForeground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension PresentationThemeList: Codable {
|
extension PresentationThemeList: Codable {
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case blocksBg
|
case blocksBg
|
||||||
@ -778,6 +805,7 @@ extension PresentationThemeList: Codable {
|
|||||||
case inputClearButton
|
case inputClearButton
|
||||||
case itemBarChart
|
case itemBarChart
|
||||||
case itemInputField
|
case itemInputField
|
||||||
|
case paymentOption
|
||||||
}
|
}
|
||||||
|
|
||||||
public convenience init(from decoder: Decoder) throws {
|
public convenience init(from decoder: Decoder) throws {
|
||||||
@ -790,6 +818,8 @@ extension PresentationThemeList: Codable {
|
|||||||
freePlainInputField = try values.decode(PresentationInputFieldTheme.self, forKey: .freeInputField)
|
freePlainInputField = try values.decode(PresentationInputFieldTheme.self, forKey: .freeInputField)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let freeTextSuccessColor = try decodeColor(values, .freeTextSuccess)
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
blocksBackgroundColor: try decodeColor(values, .blocksBg),
|
blocksBackgroundColor: try decodeColor(values, .blocksBg),
|
||||||
plainBackgroundColor: try decodeColor(values, .plainBg),
|
plainBackgroundColor: try decodeColor(values, .plainBg),
|
||||||
@ -808,7 +838,7 @@ extension PresentationThemeList: Codable {
|
|||||||
sectionHeaderTextColor: try decodeColor(values, .sectionHeaderText),
|
sectionHeaderTextColor: try decodeColor(values, .sectionHeaderText),
|
||||||
freeTextColor: try decodeColor(values, .freeText),
|
freeTextColor: try decodeColor(values, .freeText),
|
||||||
freeTextErrorColor: try decodeColor(values, .freeTextError),
|
freeTextErrorColor: try decodeColor(values, .freeTextError),
|
||||||
freeTextSuccessColor: try decodeColor(values, .freeTextSuccess),
|
freeTextSuccessColor: freeTextSuccessColor,
|
||||||
freeMonoIconColor: try decodeColor(values, .freeMonoIcon),
|
freeMonoIconColor: try decodeColor(values, .freeMonoIcon),
|
||||||
itemSwitchColors: try values.decode(PresentationThemeSwitch.self, forKey: .switch),
|
itemSwitchColors: try values.decode(PresentationThemeSwitch.self, forKey: .switch),
|
||||||
itemDisclosureActions: try values.decode(PresentationThemeItemDisclosureActions.self, forKey: .disclosureActions),
|
itemDisclosureActions: try values.decode(PresentationThemeItemDisclosureActions.self, forKey: .disclosureActions),
|
||||||
@ -821,7 +851,13 @@ extension PresentationThemeList: Codable {
|
|||||||
pageIndicatorInactiveColor: try decodeColor(values, .pageIndicatorInactive),
|
pageIndicatorInactiveColor: try decodeColor(values, .pageIndicatorInactive),
|
||||||
inputClearButtonColor: try decodeColor(values, .inputClearButton),
|
inputClearButtonColor: try decodeColor(values, .inputClearButton),
|
||||||
itemBarChart: try values.decode(PresentationThemeItemBarChart.self, forKey: .itemBarChart),
|
itemBarChart: try values.decode(PresentationThemeItemBarChart.self, forKey: .itemBarChart),
|
||||||
itemInputField: try values.decode(PresentationInputFieldTheme.self, forKey: .itemInputField)
|
itemInputField: try values.decode(PresentationInputFieldTheme.self, forKey: .itemInputField),
|
||||||
|
paymentOption: (try? values.decode(PresentationThemeList.PaymentOption.self, forKey: .paymentOption)) ?? PresentationThemeList.PaymentOption(
|
||||||
|
inactiveFillColor: freeTextSuccessColor.withMultipliedAlpha(0.3),
|
||||||
|
inactiveForegroundColor: freeTextSuccessColor,
|
||||||
|
activeFillColor: freeTextSuccessColor,
|
||||||
|
activeForegroundColor: UIColor(rgb: 0xffffff)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -165,7 +165,7 @@ public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (String, String) {
|
public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (String, String, Bool) {
|
||||||
if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] {
|
if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] {
|
||||||
var result = ""
|
var result = ""
|
||||||
if amount < 0 {
|
if amount < 0 {
|
||||||
@ -198,8 +198,8 @@ public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (St
|
|||||||
result.append(entry.symbol)
|
result.append(entry.symbol)
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
return (result, entry.symbol)
|
return (result, entry.symbol, entry.symbolOnLeft)
|
||||||
} else {
|
} else {
|
||||||
return ("", "")
|
return ("", "", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -449,8 +449,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
case let .groupPhoneCall(_, _, scheduleDate, duration):
|
case let .groupPhoneCall(_, _, scheduleDate, duration):
|
||||||
if let scheduleDate = scheduleDate {
|
if let scheduleDate = scheduleDate {
|
||||||
let timeString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: scheduleDate)
|
let timeString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: scheduleDate)
|
||||||
let titleString = strings.Notification_VoiceChatScheduled(timeString).0
|
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
let titleString = strings.Notification_VoiceChatScheduledChannel(timeString).0
|
||||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||||
|
} else {
|
||||||
|
let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
||||||
|
let titleString = strings.Notification_VoiceChatScheduled(authorName, timeString)
|
||||||
|
attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||||
|
}
|
||||||
} else if let duration = duration {
|
} else if let duration = duration {
|
||||||
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
|
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
|
||||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||||
|
|||||||
Binary file not shown.
@ -815,14 +815,14 @@ final class SharedApplicationContext {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.mainWindow.debugAction = {
|
/*self.mainWindow.debugAction = {
|
||||||
self.mainWindow.debugAction = nil
|
self.mainWindow.debugAction = nil
|
||||||
|
|
||||||
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||||
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
|
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
|
||||||
navigationController.viewControllers = [debugController(sharedContext: sharedContext, context: nil)]
|
navigationController.viewControllers = [debugController(sharedContext: sharedContext, context: nil)]
|
||||||
self.mainWindow.present(navigationController, on: .root)
|
self.mainWindow.present(navigationController, on: .root)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
presentationDataPromise.set(sharedContext.presentationData)
|
presentationDataPromise.set(sharedContext.presentationData)
|
||||||
|
|
||||||
|
|||||||
@ -357,6 +357,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private weak var sendMessageActionsController: ChatSendMessageActionSheetController?
|
private weak var sendMessageActionsController: ChatSendMessageActionSheetController?
|
||||||
private var searchResultsController: ChatSearchResultsController?
|
private var searchResultsController: ChatSearchResultsController?
|
||||||
|
|
||||||
|
private weak var currentPinchController: PinchController?
|
||||||
|
private weak var currentPinchSourceItemNode: ListViewItemNode?
|
||||||
|
|
||||||
private var screenCaptureManager: ScreenCaptureDetectionManager?
|
private var screenCaptureManager: ScreenCaptureDetectionManager?
|
||||||
private let chatAdditionalDataDisposable = MetaDisposable()
|
private let chatAdditionalDataDisposable = MetaDisposable()
|
||||||
|
|
||||||
@ -579,7 +582,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
switch error {
|
switch error {
|
||||||
case .generic:
|
case .generic, .scheduledTooLate:
|
||||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||||
case .anonymousNotAllowed:
|
case .anonymousNotAllowed:
|
||||||
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
||||||
@ -624,12 +627,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.presentAutoremoveSetup()
|
strongSelf.presentAutoremoveSetup()
|
||||||
}
|
}
|
||||||
case .paymentSent:
|
case .paymentSent:
|
||||||
for attribute in message.attributes {
|
strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
/*for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
|
//strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -920,7 +924,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let pinchController = PinchController(sourceNode: sourceNode)
|
|
||||||
|
var sourceItemNode: ListViewItemNode?
|
||||||
|
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||||
|
guard let itemNode = itemNode as? ListViewItemNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sourceNode.view.isDescendant(of: itemNode.view) {
|
||||||
|
sourceItemNode = itemNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return CGRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return strongSelf.chatDisplayNode.view.convert(strongSelf.chatDisplayNode.frameForVisibleArea(), to: nil)
|
||||||
|
})
|
||||||
|
strongSelf.currentPinchController = pinchController
|
||||||
|
strongSelf.currentPinchSourceItemNode = sourceItemNode
|
||||||
strongSelf.window?.presentInGlobalOverlay(pinchController)
|
strongSelf.window?.presentInGlobalOverlay(pinchController)
|
||||||
}, openMessageContextActions: { message, node, rect, gesture in
|
}, openMessageContextActions: { message, node, rect, gesture in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
@ -1848,7 +1871,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let invoice = media as? TelegramMediaInvoice {
|
if let invoice = media as? TelegramMediaInvoice {
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
if let receiptMessageId = invoice.receiptMessageId {
|
if let receiptMessageId = invoice.receiptMessageId {
|
||||||
strongSelf.present(BotReceiptController(context: strongSelf.context, invoice: invoice, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
} else {
|
} else {
|
||||||
strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, messageId: messageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, messageId: messageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
@ -4011,21 +4034,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (tooltipScreen, tooltipItemNode) in strongSelf.currentMessageTooltipScreens {
|
|
||||||
if let itemNode = itemNode {
|
|
||||||
if itemNode === tooltipItemNode {
|
|
||||||
tooltipScreen.addRelativeScrollingOffset(-offset, transition: transition)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tooltipScreen.addRelativeScrollingOffset(-offset, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, _, _ in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset > 0.0 {
|
if offset > 0.0 {
|
||||||
if var scrolledToMessageIdValue = strongSelf.scrolledToMessageIdValue {
|
if var scrolledToMessageIdValue = strongSelf.scrolledToMessageIdValue {
|
||||||
@ -4035,6 +4043,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
} else if offset < 0.0 {
|
} else if offset < 0.0 {
|
||||||
strongSelf.scrolledToMessageIdValue = nil
|
strongSelf.scrolledToMessageIdValue = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let currentPinchSourceItemNode = strongSelf.currentPinchSourceItemNode {
|
||||||
|
if let itemNode = itemNode {
|
||||||
|
if itemNode === currentPinchSourceItemNode {
|
||||||
|
strongSelf.currentPinchController?.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.currentPinchController?.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .pinnedMessages = self.presentationInterfaceState.subject {
|
if case .pinnedMessages = self.presentationInterfaceState.subject {
|
||||||
|
|||||||
@ -143,7 +143,6 @@ class ChatMessageShareButton: HighlightableButtonNode {
|
|||||||
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
private let pinchContainerNode: PinchSourceContainerNode
|
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
private var placeholderNode: StickerShimmerEffectNode
|
private var placeholderNode: StickerShimmerEffectNode
|
||||||
private var animationNode: GenericAnimatedStickerNode?
|
private var animationNode: GenericAnimatedStickerNode?
|
||||||
@ -196,7 +195,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
required init() {
|
required init() {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.pinchContainerNode = PinchSourceContainerNode()
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||||
|
|
||||||
@ -264,8 +262,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.containerNode.addSubnode(self.contextSourceNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.pinchContainerNode.contentNode.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.pinchContainerNode)
|
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||||
@ -281,23 +278,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
item.controllerInteraction.openMessageReactions(item.message.id)
|
item.controllerInteraction.openMessageReactions(item.message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pinchContainerNode.activate = { [weak self] sourceNode in
|
|
||||||
guard let strongSelf = self, let item = strongSelf.item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item.controllerInteraction.activateMessagePinch(sourceNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pinchContainerNode.scaleUpdated = { [weak self] scale, transition in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let factor: CGFloat = max(0.0, min(1.0, (scale - 1.0) * 8.0))
|
|
||||||
|
|
||||||
transition.updateAlpha(node: strongSelf.dateAndStatusNode, alpha: 1.0 - factor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -996,8 +976,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
|
||||||
strongSelf.pinchContainerNode.update(size: layoutSize, transition: .immediate)
|
|
||||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
|
||||||
@ -1085,7 +1063,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
imageApply()
|
imageApply()
|
||||||
|
|
||||||
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
||||||
strongSelf.pinchContainerNode.contentRect = strongSelf.imageNode.frame
|
|
||||||
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
||||||
|
|
||||||
if let updatedShareButtonNode = updatedShareButtonNode {
|
if let updatedShareButtonNode = updatedShareButtonNode {
|
||||||
|
|||||||
@ -926,7 +926,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
|
|
||||||
videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate)
|
videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate)
|
||||||
videoNode.frame = imageFrame
|
videoNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||||
|
|
||||||
if strongSelf.visibility {
|
if strongSelf.visibility {
|
||||||
if !videoNode.canAttachContent {
|
if !videoNode.canAttachContent {
|
||||||
|
|||||||
@ -21,7 +21,6 @@ private let inlineBotNameFont = nameFont
|
|||||||
class ChatMessageStickerItemNode: ChatMessageItemView {
|
class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
private let pinchContainerNode: PinchSourceContainerNode
|
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
private var placeholderNode: StickerShimmerEffectNode
|
private var placeholderNode: StickerShimmerEffectNode
|
||||||
var textNode: TextNode?
|
var textNode: TextNode?
|
||||||
@ -54,7 +53,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
required init() {
|
required init() {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.pinchContainerNode = PinchSourceContainerNode()
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.placeholderNode = StickerShimmerEffectNode()
|
self.placeholderNode = StickerShimmerEffectNode()
|
||||||
self.placeholderNode.isUserInteractionEnabled = false
|
self.placeholderNode.isUserInteractionEnabled = false
|
||||||
@ -121,8 +119,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.containerNode.addSubnode(self.contextSourceNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.pinchContainerNode.contentNode.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.pinchContainerNode)
|
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||||
@ -138,23 +135,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
item.controllerInteraction.openMessageReactions(item.message.id)
|
item.controllerInteraction.openMessageReactions(item.message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pinchContainerNode.activate = { [weak self] sourceNode in
|
|
||||||
guard let strongSelf = self, let item = strongSelf.item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item.controllerInteraction.activateMessagePinch(sourceNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pinchContainerNode.scaleUpdated = { [weak self] scale, transition in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let factor: CGFloat = max(0.0, min(1.0, (scale - 1.0) * 8.0))
|
|
||||||
|
|
||||||
transition.updateAlpha(node: strongSelf.dateAndStatusNode, alpha: 1.0 - factor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -675,12 +655,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
|
||||||
strongSelf.pinchContainerNode.update(size: layoutSize, transition: .immediate)
|
|
||||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
||||||
strongSelf.pinchContainerNode.contentRect = strongSelf.imageNode.frame
|
|
||||||
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
||||||
|
|
||||||
dateAndStatusApply(false)
|
dateAndStatusApply(false)
|
||||||
|
|||||||
@ -4029,7 +4029,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
switch error {
|
switch error {
|
||||||
case .generic:
|
case .generic, .scheduledTooLate:
|
||||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||||
case .anonymousNotAllowed:
|
case .anonymousNotAllowed:
|
||||||
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
||||||
|
|||||||
@ -16,6 +16,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
|||||||
public var enableVoipTcp: Bool
|
public var enableVoipTcp: Bool
|
||||||
public var demoVideoChats: Bool
|
public var demoVideoChats: Bool
|
||||||
public var experimentalCompatibility: Bool
|
public var experimentalCompatibility: Bool
|
||||||
|
public var enableNoiseSuppression: Bool
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -31,7 +32,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
|||||||
disableVideoAspectScaling: false,
|
disableVideoAspectScaling: false,
|
||||||
enableVoipTcp: false,
|
enableVoipTcp: false,
|
||||||
demoVideoChats: false,
|
demoVideoChats: false,
|
||||||
experimentalCompatibility: false
|
experimentalCompatibility: false,
|
||||||
|
enableNoiseSuppression: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +50,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
|||||||
disableVideoAspectScaling: Bool,
|
disableVideoAspectScaling: Bool,
|
||||||
enableVoipTcp: Bool,
|
enableVoipTcp: Bool,
|
||||||
demoVideoChats: Bool,
|
demoVideoChats: Bool,
|
||||||
experimentalCompatibility: Bool
|
experimentalCompatibility: Bool,
|
||||||
|
enableNoiseSuppression: Bool
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -63,6 +66,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
|||||||
self.enableVoipTcp = enableVoipTcp
|
self.enableVoipTcp = enableVoipTcp
|
||||||
self.demoVideoChats = demoVideoChats
|
self.demoVideoChats = demoVideoChats
|
||||||
self.experimentalCompatibility = experimentalCompatibility
|
self.experimentalCompatibility = experimentalCompatibility
|
||||||
|
self.enableNoiseSuppression = enableNoiseSuppression
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
@ -79,6 +83,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
|||||||
self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0
|
self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0
|
||||||
self.demoVideoChats = decoder.decodeInt32ForKey("demoVideoChats", orElse: 0) != 0
|
self.demoVideoChats = decoder.decodeInt32ForKey("demoVideoChats", orElse: 0) != 0
|
||||||
self.experimentalCompatibility = decoder.decodeInt32ForKey("experimentalCompatibility", orElse: 0) != 0
|
self.experimentalCompatibility = decoder.decodeInt32ForKey("experimentalCompatibility", orElse: 0) != 0
|
||||||
|
self.enableNoiseSuppression = decoder.decodeInt32ForKey("enableNoiseSuppression", orElse: 0) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
@ -97,6 +102,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
|||||||
encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp")
|
encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp")
|
||||||
encoder.encodeInt32(self.demoVideoChats ? 1 : 0, forKey: "demoVideoChats")
|
encoder.encodeInt32(self.demoVideoChats ? 1 : 0, forKey: "demoVideoChats")
|
||||||
encoder.encodeInt32(self.experimentalCompatibility ? 1 : 0, forKey: "experimentalCompatibility")
|
encoder.encodeInt32(self.experimentalCompatibility ? 1 : 0, forKey: "experimentalCompatibility")
|
||||||
|
encoder.encodeInt32(self.enableNoiseSuppression ? 1 : 0, forKey: "enableNoiseSuppression")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||||
|
|||||||
@ -180,7 +180,7 @@ public final class OngoingGroupCallContext {
|
|||||||
|
|
||||||
private var broadcastPartsSource: BroadcastPartSource?
|
private var broadcastPartsSource: BroadcastPartSource?
|
||||||
|
|
||||||
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set<UInt32>) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool) {
|
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set<UInt32>) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool, enableNoiseSuppression: Bool) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
|
||||||
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
|
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
|
||||||
@ -224,7 +224,7 @@ public final class OngoingGroupCallContext {
|
|||||||
},
|
},
|
||||||
outgoingAudioBitrateKbit: outgoingAudioBitrateKbit ?? 32,
|
outgoingAudioBitrateKbit: outgoingAudioBitrateKbit ?? 32,
|
||||||
enableVideo: enableVideo,
|
enableVideo: enableVideo,
|
||||||
enableNoiseSuppression: true
|
enableNoiseSuppression: enableNoiseSuppression
|
||||||
)
|
)
|
||||||
|
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
@ -529,10 +529,10 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set<UInt32>) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool) {
|
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set<UInt32>) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool, enableNoiseSuppression: Bool) {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, participantDescriptionsRequired: participantDescriptionsRequired, audioStreamData: audioStreamData, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: enableVideo)
|
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, participantDescriptionsRequired: participantDescriptionsRequired, audioStreamData: audioStreamData, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: enableVideo, enableNoiseSuppression: enableNoiseSuppression)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app": "7.6.2",
|
"app": "7.7",
|
||||||
"bazel": "4.0.0",
|
"bazel": "4.0.0",
|
||||||
"xcode": "12.4"
|
"xcode": "12.4"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user