mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
16c630b946
commit
648430e37d
30 changed files with 396 additions and 108 deletions
|
|
@ -1445,7 +1445,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
|
||||
func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolveUrlResult, NoError>
|
||||
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, forceUpdate: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise<Bool>?, completion: (() -> Void)?)
|
||||
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
|
||||
func openAddContact(context: AccountContext, peer: EnginePeer?, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
|
||||
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
|
||||
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
|
||||
func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void)
|
||||
|
|
@ -1550,7 +1550,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
|
||||
func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController)
|
||||
|
||||
func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController
|
||||
func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, firstName: String?, lastName: String?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController
|
||||
|
||||
func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController
|
||||
func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController
|
||||
|
|
|
|||
|
|
@ -1789,7 +1789,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
self.chatListDisplayNode.requestAddContact = { [weak self] phoneNumber in
|
||||
if let strongSelf = self {
|
||||
strongSelf.view.endEditing(true)
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
self?.present(controller, in: .window(.root), with: arguments)
|
||||
}, pushController: { [weak self] controller in
|
||||
(self?.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
|
|
|
|||
|
|
@ -1343,8 +1343,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
}
|
||||
|
||||
public override func searchTextClearTokens() {
|
||||
self.folder = nil
|
||||
self.updateSearchOptions(nil)
|
||||
// self.setQuery?(nil, [], self.searchQueryValue ?? "")
|
||||
}
|
||||
|
||||
func deleteMessages(messageIds: Set<EngineMessage.Id>?) {
|
||||
|
|
|
|||
|
|
@ -1309,7 +1309,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
var item: ChatListItem?
|
||||
public private(set) var item: ChatListItem?
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
|
|
|||
|
|
@ -365,7 +365,7 @@ public class ContactsController: ViewController {
|
|||
self.contactsNode.requestAddContact = { [weak self] phoneNumber in
|
||||
if let strongSelf = self {
|
||||
strongSelf.view.endEditing(true)
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
self?.present(controller, in: .window(.root), with: arguments)
|
||||
}, pushController: { [weak self] controller in
|
||||
(self?.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
|
|
@ -757,6 +757,8 @@ public class ContactsController: ViewController {
|
|||
let controller = strongSelf.context.sharedContext.makeNewContactScreen(
|
||||
context: strongSelf.context,
|
||||
peer: nil,
|
||||
firstName: nil,
|
||||
lastName: nil,
|
||||
phoneNumber: nil,
|
||||
shareViaException: false,
|
||||
completion: { [weak self] peer, stableId, contactData in
|
||||
|
|
|
|||
|
|
@ -310,8 +310,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
}
|
||||
didSet {
|
||||
if let scrubberView = self.scrubberView {
|
||||
if let scrubberView = self.scrubberView {
|
||||
self.view.addSubview(scrubberView)
|
||||
scrubberView.onRequestLayout = { [weak self] transition in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let requestLayout = self.requestLayout {
|
||||
requestLayout(transition)
|
||||
} else {
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(size: validLayout.0, metrics: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, bottomInset: validLayout.4, contentInset: validLayout.5, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
@ -1296,7 +1308,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
panelHeight -= 44.0
|
||||
}
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: scrubberY), size: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0))
|
||||
var scrubberFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: scrubberY), size: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0))
|
||||
if scrubberView.hasVisibleInfo {
|
||||
let infoHeight: CGFloat = 16.0
|
||||
scrubberFrame.size.height += infoHeight
|
||||
panelHeight += infoHeight
|
||||
}
|
||||
|
||||
scrubberView.updateLayout(size: scrubberFrame.size, leftInset: 0.0, rightInset: 0.0, isCollapsed: self.visibilityAlpha < 1.0, transition: transition)
|
||||
transition.updateBounds(layer: scrubberView.layer, bounds: CGRect(origin: CGPoint(), size: scrubberFrame.size))
|
||||
transition.updatePosition(layer: scrubberView.layer, position: CGPoint(x: scrubberFrame.midX, y: scrubberFrame.midY))
|
||||
|
|
|
|||
|
|
@ -38,13 +38,11 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
private var chapters: [MediaPlayerScrubbingChapter] = []
|
||||
|
||||
private var fetchStatusDisposable = MetaDisposable()
|
||||
private var scrubbingDisposable = MetaDisposable()
|
||||
private var chapterDisposable = MetaDisposable()
|
||||
private var loadingDisposable = MetaDisposable()
|
||||
|
||||
private var leftTimestampNodePushed = false
|
||||
private var rightTimestampNodePushed = false
|
||||
private var infoNodePushed = false
|
||||
|
||||
private var currentChapter: MediaPlayerScrubbingChapter?
|
||||
|
||||
|
|
@ -53,6 +51,14 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
private var currentLeftString: String?
|
||||
private var currentRightString: String?
|
||||
|
||||
var hasVisibleInfo: Bool {
|
||||
if let attributedText = self.infoNode.attributedText, !attributedText.string.isEmpty {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var hideWhenDurationIsUnknown = false {
|
||||
didSet {
|
||||
if self.hideWhenDurationIsUnknown {
|
||||
|
|
@ -72,6 +78,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
||||
var seek: (Double) -> Void = { _ in }
|
||||
|
||||
var onRequestLayout: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
init(chapters: [MediaPlayerScrubbingChapter]) {
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
|
|
@ -131,7 +139,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
self.backgroundView.contentView.addSubview(self.leftTimestampNode.view)
|
||||
self.backgroundView.contentView.addSubview(self.rightTimestampNode.view)
|
||||
self.addSubview(self.scrubberNode.view)
|
||||
//self.backgroundView.contentView.addSubview(self.infoNode.view)
|
||||
self.backgroundView.contentView.addSubview(self.infoNode.view)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
|
@ -139,7 +147,6 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
|
||||
deinit {
|
||||
self.scrubbingDisposable.dispose()
|
||||
self.fetchStatusDisposable.dispose()
|
||||
self.chapterDisposable.dispose()
|
||||
self.loadingDisposable.dispose()
|
||||
|
|
@ -148,6 +155,16 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
var isLoading = false
|
||||
var isCollapsed: Bool?
|
||||
|
||||
private func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
if let onRequestLayout = self.onRequestLayout {
|
||||
onRequestLayout(transition)
|
||||
} else {
|
||||
if let (size, leftInset, rightInset, isCollapsed) = self.containerLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateTimestampsVisibility(animated: Bool) {
|
||||
if self.isAnimatedOut {
|
||||
return
|
||||
|
|
@ -276,41 +293,11 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
strongSelf.infoNode.attributedText = NSAttributedString(string: chapter.title, font: textFont, textColor: .white)
|
||||
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
strongSelf.requestLayout(transition: .immediate)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
self.scrubbingDisposable.set((self.scrubberNode.scrubbingPosition
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let leftTimestampNodePushed: Bool = false
|
||||
let rightTimestampNodePushed: Bool = false
|
||||
let infoNodePushed: Bool
|
||||
if let value = value {
|
||||
//leftTimestampNodePushed = value < 0.16
|
||||
//rightTimestampNodePushed = value > 0.84
|
||||
infoNodePushed = value >= 0.16 && value <= 0.84
|
||||
} else {
|
||||
//leftTimestampNodePushed = false
|
||||
//rightTimestampNodePushed = false
|
||||
infoNodePushed = false
|
||||
}
|
||||
if leftTimestampNodePushed != strongSelf.leftTimestampNodePushed || rightTimestampNodePushed != strongSelf.rightTimestampNodePushed || infoNodePushed != strongSelf.infoNodePushed {
|
||||
strongSelf.leftTimestampNodePushed = leftTimestampNodePushed
|
||||
strongSelf.rightTimestampNodePushed = rightTimestampNodePushed
|
||||
strongSelf.infoNodePushed = infoNodePushed
|
||||
|
||||
if let layout = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, isCollapsed: layout.3, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func setBufferingStatusSignal(_ status: Signal<(RangeSet<Int64>, Int64)?, NoError>?) {
|
||||
|
|
@ -335,16 +322,16 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
strongSelf.infoNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .white)
|
||||
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}))
|
||||
} else if self.chapters.isEmpty {
|
||||
self.infoNode.attributedText = NSAttributedString(string: dataSizeString(fileSize, forceDecimal: true, formatting: formatting), font: textFont, textColor: .white)
|
||||
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
} else if self.chapters.isEmpty {
|
||||
self.infoNode.attributedText = nil
|
||||
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -376,27 +363,32 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
var yOffset: CGFloat = 0.0
|
||||
if !isCollapsed {
|
||||
yOffset = size.height - 44.0
|
||||
}
|
||||
|
||||
leftTimestampOffset = 14.0
|
||||
rightTimestampOffset = 14.0
|
||||
infoOffset = 0.0
|
||||
infoOffset = -8.0
|
||||
|
||||
if isCollapsed {
|
||||
scrubberLeftInset = 0.0
|
||||
scrubberRightInset = 0.0
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.leftTimestampNode.view, frame: CGRect(origin: CGPoint(x: 16.0, y: leftTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
transition.setFrame(view: self.rightTimestampNode.view, frame: CGRect(origin: CGPoint(x: size.width - leftInset - rightInset - 60.0 - 16.0, y: rightTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
transition.setFrame(view: self.leftTimestampNode.view, frame: CGRect(origin: CGPoint(x: 16.0, y: yOffset + leftTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
transition.setFrame(view: self.rightTimestampNode.view, frame: CGRect(origin: CGPoint(x: size.width - leftInset - rightInset - 60.0 - 16.0, y: yOffset + rightTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
|
||||
var infoConstrainedSize = size
|
||||
infoConstrainedSize.width = size.width - scrubberLeftInset - scrubberRightInset - 100.0
|
||||
infoConstrainedSize.width = size.width - 20.0 * 2.0
|
||||
|
||||
let infoSize = self.infoNode.measure(infoConstrainedSize)
|
||||
self.infoNode.bounds = CGRect(origin: CGPoint(), size: infoSize)
|
||||
transition.setPosition(view: self.infoNode.view, position: CGPoint(x: size.width / 2.0, y: infoOffset + infoSize.height / 2.0))
|
||||
self.infoNode.alpha = size.width < size.height && self.isCollapsed == false ? 1.0 : 0.0
|
||||
transition.setPosition(view: self.infoNode.view, position: CGPoint(x: size.width / 2.0, y: yOffset + infoOffset + infoSize.height / 2.0))
|
||||
self.infoNode.alpha = self.isCollapsed == false ? 1.0 : 0.0
|
||||
|
||||
var scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight))
|
||||
var scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: yOffset + 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight))
|
||||
if isCollapsed {
|
||||
scrubberFrame.origin.y = size.height - scrubberHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -927,8 +927,9 @@ public class GalleryController: ViewController, StandalonePresentableController,
|
|||
if case .custom = source {
|
||||
displayInfoOnTop = true
|
||||
}
|
||||
|
||||
|
||||
let syncResult = Atomic<(Bool, (() -> Void)?)>(value: (false, nil))
|
||||
var isFirstTime = true
|
||||
self.disposable.set(combineLatest(
|
||||
messageView,
|
||||
self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1),
|
||||
|
|
@ -1015,14 +1016,22 @@ public class GalleryController: ViewController, StandalonePresentableController,
|
|||
storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _, _ in },
|
||||
generateStoreAfterDownload: strongSelf.generateStoreAfterDownload,
|
||||
sendSticker: strongSelf.actionInteraction?.sendSticker,
|
||||
present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
present: { [weak strongSelf] c, a in
|
||||
if let strongSelf {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
}
|
||||
) {
|
||||
if isCentral {
|
||||
centralItemIndex = items.count
|
||||
|
||||
if isFirstTime {
|
||||
isFirstTime = false
|
||||
if item is UniversalVideoGalleryItem {
|
||||
} else {
|
||||
strongSelf.galleryNode.areControlsHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
items.append(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1516,8 +1516,22 @@ public func pushContactContextOptionsController(context: AccountContext, context
|
|||
.action(ContextMenuActionItem(text: presentationData.strings.Chat_Context_Phone_CreateNewContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
push(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in
|
||||
}), completed: nil, cancelled: nil))
|
||||
context.sharedContext.openAddContact(
|
||||
context: context,
|
||||
peer: peer,
|
||||
firstName: contactData.basicData.firstName,
|
||||
lastName: contactData.basicData.lastName,
|
||||
phoneNumber: contactData.basicData.phoneNumbers.first?.value ?? "",
|
||||
label: contactData.basicData.phoneNumbers.first?.label ?? "mobile",
|
||||
present: { [weak parentController] c, a in
|
||||
parentController?.present(c, in: .window(.root), with: a)
|
||||
},
|
||||
pushController: { c in
|
||||
push(c)
|
||||
},
|
||||
completed: {
|
||||
}
|
||||
)
|
||||
}))
|
||||
)
|
||||
items.append(
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ public func openAddPersonContactImpl(context: AccountContext, updatedPresentatio
|
|||
let controller = context.sharedContext.makeNewContactScreen(
|
||||
context: context,
|
||||
peer: peer,
|
||||
firstName: nil,
|
||||
lastName: nil,
|
||||
phoneNumber: user.phone,
|
||||
shareViaException: shareViaException,
|
||||
completion: { peer, _, _ in
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ public protocol ManagedAudioSession: AnyObject {
|
|||
func isActive() -> Signal<Bool, NoError>
|
||||
func isPlaybackActive() -> Signal<Bool, NoError>
|
||||
func isOtherAudioPlaying() -> Bool
|
||||
func didActivateWithZeroVolume() -> Signal<Void, NoError>
|
||||
|
||||
func push(params: ManagedAudioSessionClientParams) -> Disposable
|
||||
func dropAll()
|
||||
|
|
@ -311,6 +312,11 @@ public final class ManagedAudioSessionImpl: NSObject, ManagedAudioSession {
|
|||
private var isActiveValue: Bool = false
|
||||
private var callKitAudioSessionIsActive: Bool = false
|
||||
|
||||
private let zeroVolumeEvents = ValuePipe<Void>()
|
||||
public func didActivateWithZeroVolume() -> Signal<Void, NoError> {
|
||||
return self.zeroVolumeEvents.signal()
|
||||
}
|
||||
|
||||
override public init() {
|
||||
self.queue = Queue()
|
||||
|
||||
|
|
@ -1123,6 +1129,22 @@ public final class ManagedAudioSessionImpl: NSObject, ManagedAudioSession {
|
|||
if case .voiceCall = type {
|
||||
//try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.005)
|
||||
}
|
||||
|
||||
if AVAudioSession.sharedInstance().outputVolume <= 0.01 {
|
||||
let queue = self.queue
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { [weak self] in
|
||||
queue.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.currentTypeAndOutputMode != nil {
|
||||
if AVAudioSession.sharedInstance().outputVolume <= 0.01 {
|
||||
self.zeroVolumeEvents.putNext(Void())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch let error {
|
||||
managedAudioSessionLog("ManagedAudioSession activate error \(error)")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
case monoforumChats(query: String)
|
||||
}
|
||||
|
||||
public final class ScrollingState {
|
||||
fileprivate let entryId: Entry.Id
|
||||
fileprivate let entryOffset: CGFloat
|
||||
|
||||
fileprivate init(entryId: Entry.Id, entryOffset: CGFloat) {
|
||||
self.entryId = entryId
|
||||
self.entryOffset = entryOffset
|
||||
}
|
||||
}
|
||||
|
||||
public let context: AccountContext
|
||||
public let presentation: Presentation
|
||||
public let peerId: EnginePeer.Id?
|
||||
|
|
@ -81,6 +91,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
public let insets: UIEdgeInsets
|
||||
public let inputHeight: CGFloat
|
||||
public let showEmptyResults: Bool
|
||||
public let initialScrollingState: ScrollingState?
|
||||
public let messageSelected: (EngineMessage) -> Void
|
||||
public let peerSelected: (EnginePeer) -> Void
|
||||
public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?
|
||||
|
|
@ -97,6 +108,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
insets: UIEdgeInsets,
|
||||
inputHeight: CGFloat,
|
||||
showEmptyResults: Bool,
|
||||
initialScrollingState: ScrollingState?,
|
||||
messageSelected: @escaping (EngineMessage) -> Void,
|
||||
peerSelected: @escaping (EnginePeer) -> Void,
|
||||
loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?,
|
||||
|
|
@ -112,6 +124,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
self.insets = insets
|
||||
self.inputHeight = inputHeight
|
||||
self.showEmptyResults = showEmptyResults
|
||||
self.initialScrollingState = initialScrollingState
|
||||
self.messageSelected = messageSelected
|
||||
self.peerSelected = peerSelected
|
||||
self.loadTagMessages = loadTagMessages
|
||||
|
|
@ -146,7 +159,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
return true
|
||||
}
|
||||
|
||||
private enum Entry: Equatable, Comparable {
|
||||
fileprivate enum Entry: Equatable, Comparable {
|
||||
enum Id: Hashable {
|
||||
case peer(EnginePeer.Id)
|
||||
case message(EngineMessage.Id)
|
||||
|
|
@ -373,6 +386,26 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
return result
|
||||
}
|
||||
|
||||
public func scrollingState() -> ScrollingState? {
|
||||
var scrollingState: ScrollingState?
|
||||
self.listNode.forEachVisibleItemNode { itemNode in
|
||||
if scrollingState != nil {
|
||||
return
|
||||
}
|
||||
if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
|
||||
switch item.content {
|
||||
case let .peer(peerData):
|
||||
if let message = peerData.messages.first {
|
||||
scrollingState = ScrollingState(entryId: .message(message.id), entryOffset: itemNode.frame.minY - self.listNode.insets.top)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return scrollingState
|
||||
}
|
||||
|
||||
func update(component: ChatInlineSearchResultsListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
|
|
@ -1127,6 +1160,22 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
}
|
||||
self.hintAnimateListTransition = false
|
||||
|
||||
if previousComponent == nil, let initialScrollingState = component.initialScrollingState {
|
||||
var index = 0
|
||||
for entry in contentsState.entries {
|
||||
if entry.id == initialScrollingState.entryId {
|
||||
scrollToItem = ListViewScrollToItem(
|
||||
index: index,
|
||||
position: .top(initialScrollingState.entryOffset),
|
||||
animated: false,
|
||||
curve: .Default(duration: nil),
|
||||
directionHint: .Up
|
||||
)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.transaction(
|
||||
deleteIndices: deleteIndices.map { index in
|
||||
return ListViewDeleteItem(index: index, directionHint: nil)
|
||||
|
|
|
|||
|
|
@ -1366,8 +1366,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
|
||||
switch action {
|
||||
case let .action(f):
|
||||
f.action()
|
||||
recognizer.cancel()
|
||||
if let actionWithLongTapRecognizer = f.actionWithLongTapRecognizer {
|
||||
actionWithLongTapRecognizer(recognizer)
|
||||
} else {
|
||||
f.action()
|
||||
}
|
||||
case let .optionalAction(f):
|
||||
f()
|
||||
recognizer.cancel()
|
||||
|
|
@ -5949,12 +5952,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
break
|
||||
case let .url(url):
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({ [weak self] in
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
let cleanUrl = url.url.replacingOccurrences(of: "mailto:", with: "")
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(cleanUrl, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
|
|
|
|||
|
|
@ -623,10 +623,12 @@ public final class ChatMessageAccessibilityData {
|
|||
public enum InternalBubbleTapAction {
|
||||
public struct Action {
|
||||
public var action: () -> Void
|
||||
public var actionWithLongTapRecognizer: ((TapLongTapOrDoubleTapGestureRecognizer) -> Void)?
|
||||
public var contextMenuOnLongPress: Bool
|
||||
|
||||
public init(_ action: @escaping () -> Void, contextMenuOnLongPress: Bool = false) {
|
||||
public init(_ action: @escaping () -> Void, actionWithLongTapRecognizer: ((TapLongTapOrDoubleTapGestureRecognizer) -> Void)? = nil, contextMenuOnLongPress: Bool = false) {
|
||||
self.action = action
|
||||
self.actionWithLongTapRecognizer = actionWithLongTapRecognizer
|
||||
self.contextMenuOnLongPress = contextMenuOnLongPress
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
} else {
|
||||
currentMaxGlyphCount = nil
|
||||
}
|
||||
let previousGlyphCount = self.textNode.textNode.getGlyphCount()
|
||||
let previousGlyphCount = self.textNode.textNode.getCharacterToGlyphMapping().count
|
||||
|
||||
return { item, layoutConstants, _, _, _, _ in
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
|
|
@ -828,7 +828,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
|
||||
var previousAnimateGlyphCount: Int?
|
||||
if hasDraft || hadDraft {
|
||||
previousAnimateGlyphCount = strongSelf.textNode.textNode.getGlyphCount()
|
||||
previousAnimateGlyphCount = strongSelf.textNode.textNode.getCharacterToGlyphMapping().count
|
||||
}
|
||||
|
||||
strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview
|
||||
|
|
@ -1065,7 +1065,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
|
||||
private func updateTextRevealAnimation(previousGlyphCount: Int) {
|
||||
var fromCount = previousGlyphCount
|
||||
let toCount = self.textNode.textNode.getGlyphCount()
|
||||
let toCount = self.textNode.textNode.getCharacterToGlyphMapping().count
|
||||
let timestamp = CACurrentMediaTime()
|
||||
if let textRevealAnimationState = self.textRevealAnimationState {
|
||||
if textRevealAnimationState.toCount == toCount {
|
||||
|
|
@ -1114,7 +1114,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
var requestUpdate = false
|
||||
let glyphCount = textRevealAnimationState.glyphCount(timestamp: timestamp)
|
||||
if let revealGlyphCount = self.textNode.textNode.revealGlyphCount, let cachedLayout = self.textNode.textNode.cachedLayout {
|
||||
if cachedLayout.sizeForGlyphCount(glyphCount: revealGlyphCount).height != cachedLayout.sizeForGlyphCount(glyphCount: glyphCount).height {
|
||||
let previousLayout = cachedLayout.layoutForGlyphCount(glyphCount: revealGlyphCount)
|
||||
let updatedLayout = cachedLayout.layoutForGlyphCount(glyphCount: glyphCount)
|
||||
|
||||
if updatedLayout.size.height != previousLayout.size.height || abs(updatedLayout.size.width - previousLayout.size.width) > 8.0 {
|
||||
if lineUpdateTimeout >= 0.0 {
|
||||
lastLineUpdateTimestamp = timestamp
|
||||
requestUpdate = true
|
||||
|
|
|
|||
|
|
@ -164,12 +164,14 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||
public var contentNode: ContextExtractedContentContainingNode?
|
||||
public var messageNode: ASDisplayNode?
|
||||
public var progress: Promise<Bool>?
|
||||
public var gesture: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
public init(message: Message? = nil, contentNode: ContextExtractedContentContainingNode? = nil, messageNode: ASDisplayNode? = nil, progress: Promise<Bool>? = nil) {
|
||||
public init(message: Message? = nil, contentNode: ContextExtractedContentContainingNode? = nil, messageNode: ASDisplayNode? = nil, progress: Promise<Bool>? = nil, gesture: TapLongTapOrDoubleTapGestureRecognizer? = nil) {
|
||||
self.message = message
|
||||
self.contentNode = contentNode
|
||||
self.messageNode = messageNode
|
||||
self.progress = progress
|
||||
self.gesture = gesture
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,10 +183,10 @@ final class NewContactScreenComponent: Component {
|
|||
|
||||
}
|
||||
|
||||
func updateCountryCode(code: Int32, name: String) {
|
||||
func updateCountryCode(code: Int32, name: String, phoneNumber: String?) {
|
||||
if let view = self.phoneSection.findTaggedView(tag: self.phoneTag) as? ListItemComponentAdaptor.View {
|
||||
if let itemNode = view.itemNode as? PhoneInputItemNode {
|
||||
itemNode.updateCountryCode(code: code, name: name)
|
||||
itemNode.updateCountryCode(code: code, name: name, phoneNumber: phoneNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -256,6 +256,7 @@ final class NewContactScreenComponent: Component {
|
|||
let strings = environment.strings
|
||||
|
||||
var initialCountryCode: Int32?
|
||||
var initialPhoneNumberWithoutCountryCode: String?
|
||||
var updateFocusTag: Any?
|
||||
if self.component == nil {
|
||||
if let peer = component.initialData.peer {
|
||||
|
|
@ -279,6 +280,9 @@ final class NewContactScreenComponent: Component {
|
|||
} else {
|
||||
updateFocusTag = self.firstNameTag
|
||||
}
|
||||
if phone.hasPrefix("\(countryCode)") {
|
||||
initialPhoneNumberWithoutCountryCode = String(phone[phone.index(phone.startIndex, offsetBy: "\(countryCode)".count)...])
|
||||
}
|
||||
} else {
|
||||
countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
|
||||
updateFocusTag = self.phoneTag
|
||||
|
|
@ -456,7 +460,7 @@ final class NewContactScreenComponent: Component {
|
|||
itemGenerator: PhoneInputItem(
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
value: (initialCountryCode, nil, ""),
|
||||
value: (initialCountryCode, nil, initialPhoneNumberWithoutCountryCode ?? ""),
|
||||
accessory: phoneAccesory,
|
||||
selectCountryCode: { [weak self] in
|
||||
guard let self, let environment = self.environment, let controller = environment.controller() else {
|
||||
|
|
@ -467,7 +471,7 @@ final class NewContactScreenComponent: Component {
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateCountryCode(code: Int32(code), name: name)
|
||||
self.updateCountryCode(code: Int32(code), name: name, phoneNumber: nil)
|
||||
self.activateInput(tag: self.phoneTag)
|
||||
}
|
||||
self.deactivateInput()
|
||||
|
|
@ -616,9 +620,8 @@ final class NewContactScreenComponent: Component {
|
|||
contentHeight += sectionSpacing
|
||||
|
||||
if let initialCountryCode {
|
||||
self.updateCountryCode(code: initialCountryCode, name: "")
|
||||
self.updateCountryCode(code: initialCountryCode, name: "", phoneNumber: initialPhoneNumberWithoutCountryCode)
|
||||
}
|
||||
|
||||
|
||||
var optionsSectionItems: [AnyComponentWithIdentity<Empty>] = [
|
||||
AnyComponentWithIdentity(id: "syncContact", component: AnyComponent(ListActionItemComponent(
|
||||
|
|
@ -1033,22 +1036,24 @@ public class NewContactScreen: ViewControllerComponentContainer {
|
|||
|
||||
public static func initialData(
|
||||
peer: EnginePeer? = nil,
|
||||
firstName: String? = nil,
|
||||
lastName: String? = nil,
|
||||
phoneNumber: String? = nil,
|
||||
shareViaException: Bool = false
|
||||
) -> InitialData {
|
||||
if case let .user(user) = peer {
|
||||
return InitialData(
|
||||
peer: peer,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
firstName: firstName ?? user.firstName,
|
||||
lastName: lastName ?? user.lastName,
|
||||
phoneNumber: user.phone ?? phoneNumber,
|
||||
shareViaException: shareViaException
|
||||
)
|
||||
} else {
|
||||
return InitialData(
|
||||
peer: nil,
|
||||
firstName: nil,
|
||||
lastName: nil,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
phoneNumber: phoneNumber,
|
||||
shareViaException: false
|
||||
)
|
||||
|
|
|
|||
|
|
@ -296,8 +296,8 @@ final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.phoneInputNode.codeAndNumber = self.phoneInputNode.codeAndNumber
|
||||
}
|
||||
|
||||
func updateCountryCode(code: Int32, name: String) {
|
||||
self.phoneInputNode.codeAndNumber = (code, name, self.phoneInputNode.codeAndNumber.2)
|
||||
func updateCountryCode(code: Int32, name: String, phoneNumber: String? = nil) {
|
||||
self.phoneInputNode.codeAndNumber = (code, name, phoneNumber ?? self.phoneInputNode.codeAndNumber.2)
|
||||
let _ = self.processNumberChange(self.phoneInputNode.number)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -418,6 +418,7 @@ public final class InteractiveTextNodeLayout: NSObject {
|
|||
fileprivate let textStroke: (UIColor, CGFloat)?
|
||||
public let displayContentsUnderSpoilers: Bool
|
||||
fileprivate let expandedBlocks: Set<Int>
|
||||
fileprivate var characterToGlyphMapping: [Int]?
|
||||
|
||||
fileprivate init(
|
||||
attributedString: NSAttributedString?,
|
||||
|
|
@ -1045,9 +1046,22 @@ public final class InteractiveTextNodeLayout: NSObject {
|
|||
if !self.segments.isEmpty, let line = self.segments[0].lines.first {
|
||||
height = line.frame.maxY
|
||||
}
|
||||
|
||||
var actualCount = 0
|
||||
var itemCount = 0
|
||||
for item in self.getCharacterToGlyphMapping() {
|
||||
if itemCount >= glyphCount {
|
||||
break
|
||||
}
|
||||
actualCount = item
|
||||
itemCount += 1
|
||||
}
|
||||
let glyphCount = actualCount
|
||||
|
||||
var width: CGFloat = 0.0
|
||||
var count = 0
|
||||
var trailingLineWidth: CGFloat = 0.0
|
||||
var lineCount = 0
|
||||
for segment in self.segments {
|
||||
for line in segment.lines {
|
||||
if count >= glyphCount {
|
||||
|
|
@ -1094,13 +1108,18 @@ public final class InteractiveTextNodeLayout: NSObject {
|
|||
}
|
||||
|
||||
width = max(width, lineWidth)
|
||||
trailingLineWidth = lineWidth
|
||||
trailingLineWidth = lineWidth + self.insets.left + self.insets.right + 4.0
|
||||
lineCount += 1
|
||||
}
|
||||
}
|
||||
let _ = width
|
||||
width = ceil(width) + self.insets.left + self.insets.right
|
||||
if lineCount > 1 {
|
||||
width = self.size.width
|
||||
}
|
||||
|
||||
height += self.insets.top + self.insets.bottom + 2.0
|
||||
return TextNodeLayout.LayoutInfo(
|
||||
size: CGSize(width: self.size.width, height: ceil(height)),
|
||||
size: CGSize(width: width, height: ceil(height)),
|
||||
trailingLineWidth: trailingLineWidth
|
||||
)
|
||||
}
|
||||
|
|
@ -1108,6 +1127,62 @@ public final class InteractiveTextNodeLayout: NSObject {
|
|||
public func sizeForGlyphCount(glyphCount: Int) -> CGSize {
|
||||
return self.layoutForGlyphCount(glyphCount: glyphCount).size
|
||||
}
|
||||
|
||||
public func getCharacterToGlyphMapping() -> [Int] {
|
||||
guard let attributedString = self.attributedString else {
|
||||
return []
|
||||
}
|
||||
if let value = self.characterToGlyphMapping {
|
||||
return value
|
||||
}
|
||||
|
||||
let nsString = attributedString.string as NSString
|
||||
let fullLength = nsString.length
|
||||
|
||||
// Build a map from each composed character start index to its cumulative glyph count.
|
||||
// Walk glyphs in reveal order and record the glyph count after processing each composed character.
|
||||
var glyphCountByComposedStart: [Int: Int] = [:]
|
||||
var cumulativeGlyphCount = 0
|
||||
|
||||
for segment in self.segments {
|
||||
for line in segment.lines {
|
||||
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
|
||||
for run in glyphRuns {
|
||||
let run = run as! CTRun
|
||||
let glyphCount = CTRunGetGlyphCount(run)
|
||||
|
||||
var stringIndices = [CFIndex](repeating: 0, count: glyphCount)
|
||||
CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), &stringIndices)
|
||||
|
||||
for i in 0..<glyphCount {
|
||||
cumulativeGlyphCount += 1
|
||||
let stringIndex = stringIndices[i]
|
||||
let composedRange = nsString.rangeOfComposedCharacterSequence(at: stringIndex)
|
||||
glyphCountByComposedStart[composedRange.location] = cumulativeGlyphCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the source string by composed character sequences and emit an entry for each.
|
||||
// Characters consumed by a ligature (no dedicated glyphs) get the same cumulative count
|
||||
// as the preceding character, so the animation still steps through them.
|
||||
var result: [Int] = []
|
||||
var lastKnownGlyphCount = 0
|
||||
var index = 0
|
||||
while index < fullLength {
|
||||
let composedRange = nsString.rangeOfComposedCharacterSequence(at: index)
|
||||
if let count = glyphCountByComposedStart[composedRange.location] {
|
||||
lastKnownGlyphCount = count
|
||||
}
|
||||
result.append(lastKnownGlyphCount)
|
||||
index = composedRange.location + composedRange.length
|
||||
}
|
||||
|
||||
self.characterToGlyphMapping = result
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private func addSpoiler(line: InteractiveTextNodeLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int) {
|
||||
|
|
@ -2179,7 +2254,14 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||
|
||||
return count
|
||||
}
|
||||
|
||||
|
||||
public func getCharacterToGlyphMapping() -> [Int] {
|
||||
guard let cachedLayout = self.cachedLayout else {
|
||||
return []
|
||||
}
|
||||
return cachedLayout.getCharacterToGlyphMapping()
|
||||
}
|
||||
|
||||
public func updateRevealGlyphCount(count: Int?) {
|
||||
guard let cachedLayout = self.cachedLayout else {
|
||||
return
|
||||
|
|
@ -2187,7 +2269,18 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||
|
||||
self.revealGlyphCount = count
|
||||
|
||||
if let count {
|
||||
if var count {
|
||||
var actualCount = 0
|
||||
var itemCount = 0
|
||||
for item in self.getCharacterToGlyphMapping() {
|
||||
if itemCount >= count {
|
||||
break
|
||||
}
|
||||
actualCount = item
|
||||
itemCount += 1
|
||||
}
|
||||
count = actualCount
|
||||
|
||||
var nextItemId = 0
|
||||
var currentCount = 0
|
||||
for segment in cachedLayout.segments {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ extension PeerInfoScreenNode {
|
|||
}, openAd: { _ in
|
||||
}, addContact: { [weak self] phoneNumber in
|
||||
if let strongSelf = self {
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
self?.controller?.present(controller, in: .window(.root), with: arguments)
|
||||
}, pushController: { [weak self] controller in
|
||||
if let strongSelf = self {
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ extension ChatControllerImpl {
|
|||
openText = self.presentationData.strings.Conversation_FileOpenIn
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
|
||||
let source: ContextContentSource
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ extension ChatControllerImpl: MFMessageComposeViewControllerDelegate {
|
|||
])
|
||||
let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
|
||||
pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: nil, contactData: contactData, parentController: self, push: { [weak self] c in
|
||||
pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: peer, contactData: contactData, parentController: self, push: { [weak self] c in
|
||||
self?.push(c)
|
||||
})
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1405,14 +1405,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
if case .current = i {
|
||||
c.presentationArguments = a
|
||||
c.statusBar.alphaUpdated = { [weak self] transition in
|
||||
guard let self else {
|
||||
return
|
||||
if c is UndoOverlayController {
|
||||
self.present(c, in: .current)
|
||||
} else {
|
||||
c.presentationArguments = a
|
||||
c.statusBar.alphaUpdated = { [weak self] transition in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateStatusBarPresentation(animated: transition.isAnimated)
|
||||
}
|
||||
self.updateStatusBarPresentation(animated: transition.isAnimated)
|
||||
self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {})
|
||||
}
|
||||
self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {})
|
||||
} else {
|
||||
self.present(c, in: .window(.root), with: a, blockInteraction: true)
|
||||
}
|
||||
|
|
@ -3617,7 +3621,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}, addContact: { [weak self] phoneNumber in
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||
self?.present(controller, in: .window(.root), with: arguments)
|
||||
}, pushController: { [weak self] controller in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -9105,6 +9109,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
let controller = self.context.sharedContext.makeNewContactScreen(
|
||||
context: self.context,
|
||||
peer: EnginePeer(peer),
|
||||
firstName: nil,
|
||||
lastName: nil,
|
||||
phoneNumber: nil,
|
||||
shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact),
|
||||
completion: { [weak self] peer, _, _ in
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
var inlineSearchResults: ComponentView<Empty>?
|
||||
private var inlineSearchResultsReadyDisposable: Disposable?
|
||||
private var inlineSearchResultsReady: Bool = false
|
||||
private var inlineSearchResultsScrollingState: (domain: ChatSearchDomain, query: String, state: ChatInlineSearchResultsListComponent.ScrollingState)?
|
||||
|
||||
var isScrollingLockedAtTop: Bool = false
|
||||
|
||||
|
|
@ -3050,6 +3051,16 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
showNavigateButtons = false
|
||||
}
|
||||
|
||||
if let inlineSearchResultsScrollingState = self.inlineSearchResultsScrollingState {
|
||||
if let search = self.chatPresentationInterfaceState.search {
|
||||
if search.domain != inlineSearchResultsScrollingState.domain || search.query != inlineSearchResultsScrollingState.query {
|
||||
self.inlineSearchResultsScrollingState = nil
|
||||
}
|
||||
} else {
|
||||
self.inlineSearchResultsScrollingState = nil
|
||||
}
|
||||
}
|
||||
|
||||
if displayInlineSearch {
|
||||
let peerId = self.chatPresentationInterfaceState.chatLocation.peerId
|
||||
|
||||
|
|
@ -3107,6 +3118,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
insets: childContentInsets,
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
showEmptyResults: self.showListEmptyResults,
|
||||
initialScrollingState: self.inlineSearchResultsScrollingState?.state,
|
||||
messageSelected: { [weak self] message in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -3385,6 +3397,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
environment: {},
|
||||
containerSize: layout.size
|
||||
)
|
||||
self.inlineSearchResultsScrollingState = nil
|
||||
if let inlineSearchResultsView = inlineSearchResults.view as? ChatInlineSearchResultsListComponent.View {
|
||||
var animateIn = false
|
||||
if inlineSearchResultsView.superview == nil {
|
||||
|
|
@ -3438,6 +3451,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
if let inlineSearchResults = self.inlineSearchResults {
|
||||
self.inlineSearchResults = nil
|
||||
if let inlineSearchResultsView = inlineSearchResults.view as? ChatInlineSearchResultsListComponent.View {
|
||||
|
||||
if let search = self.chatPresentationInterfaceState.search, let scrollingState = inlineSearchResultsView.scrollingState() {
|
||||
self.inlineSearchResultsScrollingState = (search.domain, search.query, scrollingState)
|
||||
}
|
||||
|
||||
transition.updateAlpha(layer: inlineSearchResultsView.layer, alpha: 0.0, completion: { [weak inlineSearchResultsView] _ in
|
||||
inlineSearchResultsView?.removeFromSuperview()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
var tapButtonRightInset: CGFloat = rightInset
|
||||
|
||||
let buttonsContainerSize = CGSize(width: 16.0, height: panelHeight)
|
||||
self.buttonsContainer.frame = CGRect(origin: CGPoint(x: width - buttonsContainerSize.width - rightInset, y: 0.0), size: buttonsContainerSize)
|
||||
self.buttonsContainer.frame = CGRect(origin: CGPoint(x: width - buttonsContainerSize.width - rightInset - 4.0, y: 0.0), size: buttonsContainerSize)
|
||||
|
||||
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
|
|
@ -608,7 +608,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
let previousMediaReference = self.previousMediaReference
|
||||
let context = self.context
|
||||
|
||||
let contentLeftInset: CGFloat = leftInset + 10.0
|
||||
let contentLeftInset: CGFloat = leftInset + 18.0
|
||||
var textLineInset: CGFloat = 10.0
|
||||
var rightInset: CGFloat = 14.0 + rightInset
|
||||
|
||||
|
|
@ -707,7 +707,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
var applyImage: (() -> Void)?
|
||||
if let imageDimensions = imageDimensions {
|
||||
let boundingSize = CGSize(width: 35.0, height: 35.0)
|
||||
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
|
||||
applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
|
||||
|
||||
textLineInset += 9.0 + 35.0
|
||||
}
|
||||
|
|
@ -885,8 +885,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
transition: animationTransition
|
||||
)
|
||||
|
||||
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 8.0), size: CGSize(width: 35.0, height: 35.0))
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0))
|
||||
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 36.0, height: 36.0))
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 36.0, height: 36.0))
|
||||
|
||||
if let applyImage = applyImage {
|
||||
applyImage()
|
||||
|
|
|
|||
|
|
@ -202,6 +202,8 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
|||
let controller = strongSelf.context.sharedContext.makeNewContactScreen(
|
||||
context: strongSelf.context,
|
||||
peer: nil,
|
||||
firstName: nil,
|
||||
lastName: nil,
|
||||
phoneNumber: nil,
|
||||
shareViaException: false,
|
||||
completion: { [weak self] peer, stableId, contactData in
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import ShareController
|
|||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import PeerInfoUI
|
||||
import PhoneNumberFormat
|
||||
|
||||
func openAddContactImpl(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!<Mobile>!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) {
|
||||
func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!<Mobile>!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) {
|
||||
let _ = (DeviceAccess.authorizationStatus(subject: .contacts)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { value in
|
||||
|
|
@ -17,8 +18,10 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam
|
|||
case .allowed:
|
||||
let controller = context.sharedContext.makeNewContactScreen(
|
||||
context: context,
|
||||
peer: nil,
|
||||
phoneNumber: phoneNumber,
|
||||
peer: peer,
|
||||
firstName: firstName.isEmpty ? nil : firstName,
|
||||
lastName: lastName.isEmpty ? nil : lastName,
|
||||
phoneNumber: cleanPhoneNumber(phoneNumber, removePlus: true),
|
||||
shareViaException: false,
|
||||
completion: { peer, stableId, contactData in
|
||||
if let peer = peer {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import Foundation
|
||||
import MediaPlayer
|
||||
import AVFAudio
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
|
|
@ -318,6 +320,45 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||
playerType = (file.isVoice || file.isInstantVideo) ? .voice : .file
|
||||
}
|
||||
params.context.sharedContext.mediaManager.setPlaylist((params.context, PeerMessagesMediaPlaylist(context: params.context, location: location, chatLocationContextHolder: params.chatLocationContextHolder)), type: playerType, control: control)
|
||||
|
||||
if case .voice = playerType {
|
||||
let _ = (params.context.sharedContext.mediaManager.audioSession.didActivateWithZeroVolume() |> map { _ -> Bool in
|
||||
return true
|
||||
} |> take(1) |> timeout(1.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStandalone(next: { value in
|
||||
if value {
|
||||
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
|
||||
//TODO:localize
|
||||
let toastController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Device is muted.", timeout: 4.0, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
})
|
||||
params.present(toastController, nil, .current)
|
||||
let isNotMuted: Signal<Bool, NoError> = Signal { subscriber in
|
||||
subscriber.putNext(AVAudioSession.sharedInstance().outputVolume > 0.01)
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
let _ = isNotMuted
|
||||
|
||||
let volumeView = MPVolumeView()
|
||||
if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
let value = AVAudioSession.sharedInstance().outputVolume
|
||||
slider.setValue(AVAudioSession.sharedInstance().outputVolume + 0.01, animated: false)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
slider.setValue(value, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (isNotMuted |> filter({ $0 }) |> delay(0.1, queue: .mainQueue()) |> restart |> take(1) |> timeout(5.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStandalone(next: { [weak toastController] value in
|
||||
if value {
|
||||
toastController?.dismiss()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
case let .story(storyController):
|
||||
params.dismissInput()
|
||||
|
|
|
|||
|
|
@ -765,6 +765,7 @@ func openResolvedUrlImpl(
|
|||
if case .new = section {
|
||||
context.sharedContext.openAddContact(
|
||||
context: context,
|
||||
peer: nil,
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
phoneNumber: "",
|
||||
|
|
@ -841,6 +842,7 @@ func openResolvedUrlImpl(
|
|||
case .contact:
|
||||
context.sharedContext.openAddContact(
|
||||
context: context,
|
||||
peer: nil,
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
phoneNumber: "",
|
||||
|
|
|
|||
|
|
@ -2323,8 +2323,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return LocalizationListController(context: context)
|
||||
}
|
||||
|
||||
public func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) {
|
||||
openAddContactImpl(context: context, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, label: label, present: present, pushController: pushController, completed: completed)
|
||||
public func openAddContact(context: AccountContext, peer: EnginePeer?, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) {
|
||||
openAddContactImpl(context: context, peer: peer, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, label: label, present: present, pushController: pushController, completed: completed)
|
||||
}
|
||||
|
||||
public func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
|
|
@ -4341,8 +4341,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return ChannelMembersSearchControllerImpl(params: params)
|
||||
}
|
||||
|
||||
public func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController {
|
||||
return NewContactScreen(context: context, initialData: NewContactScreen.initialData(peer: peer, phoneNumber: phoneNumber, shareViaException: shareViaException), completion: completion)
|
||||
public func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, firstName: String?, lastName: String?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController {
|
||||
return NewContactScreen(context: context, initialData: NewContactScreen.initialData(peer: peer, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, shareViaException: shareViaException), completion: completion)
|
||||
}
|
||||
|
||||
public func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue