Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-09-17 21:25:13 +03:00
parent 151a472294
commit 1f285271b2
22 changed files with 407 additions and 91 deletions

View File

@ -6622,7 +6622,9 @@ Sorry for the inconvenience.";
"VoiceChat.VideoPreviewShareScreenInfo" = "Everything on your screen\nwill be shared";
"Gallery.SaveToGallery" = "Save to Gallery";
"Gallery.ImageSaved" = "Image Saved";
"Gallery.VideoSaved" = "Video Saved";
"Gallery.ImagesAndVideosSaved" = "Media Saved";
"Gallery.WaitForVideoDownoad" = "Please wait for the video to be fully downloaded.";
"Gallery.SaveImage" = "Save Image";
@ -6832,3 +6834,30 @@ Ads should no longer be synonymous with abuse of user privacy. Let us redefine h
"CHAT_MESSAGE_NOTHEME" = "%1$@ set theme to default one in the group %2$@";
"Activity.EnjoyingAnimations" = "enjoying %@ animations";
"Conversation.InteractiveEmojiSyncTip" = "If %@ was viewing the chat right now, he would also enjoy this animation.";
"Contacts.Search.NoResults" = "No Results";
"Contacts.Search.NoResultsQueryDescription" = "There were no results for \"%@\".\nTry a new search.";
"LiveStream.Listening.Members_0" = "%@ listening";
"LiveStream.Listening.Members_1" = "%@ listening";
"LiveStream.Listening.Members_2" = "%@ listening";
"LiveStream.Listening.Members_3_10" = "%@ listening";
"LiveStream.Listening.Members_many" = "%@ listening";
"LiveStream.Listening.Members_any" = "%@ listening";
"LiveStream.Watching.Members_0" = "%@ watching";
"LiveStream.Watching.Members_1" = "%@ watching";
"LiveStream.Watching.Members_2" = "%@ watching";
"LiveStream.Watching.Members_3_10" = "%@ watching";
"LiveStream.Watching.Members_many" = "%@ watching";
"LiveStream.Watching.Members_any" = "%@ watching";
"VoiceChat.RecordTitle" = "Record Video Chat";
"LiveStream.RecordTitle" = "Record Live Stream";
"VoiceChat.RecordVideoAndAudio" = "Video and Audio";
"VoiceChat.RecordOnlyAudio" = "Only Audio";
"VoiceChat.RecordPortrait" = "Portrait";
"VoiceChat.RecordLandscape" = "Landscape";
"VoiceChat.RecordStartRecording" = "Start Recording";

View File

@ -34,6 +34,8 @@ swift_library(
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/StickerResources:StickerResources",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
],
visibility = [
"//visibility:public",

View File

@ -15,6 +15,8 @@ import ContactsPeerItem
import ContextUI
import PhoneNumberFormat
import ItemListUI
import AnimatedStickerNode
import TelegramAnimatedStickerNode
private enum ContactListSearchGroup {
case contacts
@ -175,16 +177,18 @@ struct ContactListSearchContainerTransition {
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
let isSearching: Bool
let emptyResults: Bool
let query: String
}
private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition {
private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, addContact: addContact, openPeer: openPeer, contextAction: contextAction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, addContact: addContact, openPeer: openPeer, contextAction: contextAction), directionHint: nil) }
return ContactListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
return ContactListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, emptyResults: emptyResults, query: query)
}
public struct ContactsSearchCategories: OptionSet {
@ -208,6 +212,11 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
private let dimNode: ASDisplayNode
public let listNode: ListView
private let emptyResultsTitleNode: ImmediateTextNode
private let emptyResultsTextNode: ImmediateTextNode
private let emptyResultsAnimationNode: AnimatedStickerNode
private var emptyResultsAnimationSize: CGSize = CGSize()
private let searchQuery = Promise<String?>()
private let searchDisposable = MetaDisposable()
@ -241,14 +250,36 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
}
self.emptyResultsTitleNode = ImmediateTextNode()
self.emptyResultsTitleNode.displaysAsynchronously = false
self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Contacts_Search_NoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor)
self.emptyResultsTitleNode.textAlignment = .center
self.emptyResultsTitleNode.isHidden = true
self.emptyResultsTextNode = ImmediateTextNode()
self.emptyResultsTextNode.displaysAsynchronously = false
self.emptyResultsTextNode.maximumNumberOfLines = 0
self.emptyResultsTextNode.textAlignment = .center
self.emptyResultsTextNode.isHidden = true
self.emptyResultsAnimationNode = AnimatedStickerNode()
self.emptyResultsAnimationNode.isHidden = true
super.init()
self.emptyResultsAnimationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListNoResults"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.emptyResultsAnimationSize = CGSize(width: 148.0, height: 148.0)
self.backgroundColor = nil
self.isOpaque = false
self.addSubnode(self.dimNode)
self.addSubnode(self.listNode)
self.addSubnode(self.emptyResultsAnimationNode)
self.addSubnode(self.emptyResultsTitleNode)
self.addSubnode(self.emptyResultsTextNode)
self.listNode.isHidden = true
let themeAndStringsPromise = self.themeAndStringsPromise
@ -256,7 +287,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
let previousFoundRemoteContacts = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
let searchItems = self.searchQuery.get()
|> mapToSignal { query -> Signal<[ContactListSearchEntry]?, NoError> in
|> mapToSignal { query -> Signal<([ContactListSearchEntry]?, String), NoError> in
if let query = query, !query.isEmpty {
let foundLocalContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError>
if categories.contains(.cloudContacts) {
@ -286,7 +317,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
return combineLatest(foundLocalContacts, foundRemoteContacts, foundDeviceContacts, themeAndStringsPromise.get())
|> delay(0.1, queue: Queue.concurrentDefaultQueue())
|> map { localPeersAndPresences, remotePeers, deviceContacts, themeAndStrings -> [ContactListSearchEntry] in
|> map { localPeersAndPresences, remotePeers, deviceContacts, themeAndStrings -> ([ContactListSearchEntry], String) in
let _ = previousFoundRemoteContacts.swap(remotePeers)
var entries: [ContactListSearchEntry] = []
@ -381,18 +412,18 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
entries.append(.addContact(themeAndStrings.0, themeAndStrings.1, query))
}
return entries
return (entries, query)
}
} else {
let _ = previousFoundRemoteContacts.swap(nil)
return .single(nil)
return .single((nil, ""))
}
}
let previousSearchItems = Atomic<[ContactListSearchEntry]>(value: [])
self.searchDisposable.set((searchItems
|> deliverOnMainQueue).start(next: { [weak self] items in
|> deliverOnMainQueue).start(next: { [weak self] items, query in
if let strongSelf = self {
let previousItems = previousSearchItems.swap(items ?? [])
@ -404,7 +435,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
}
}
let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, timeFormat: strongSelf.presentationData.dateTimeFormat, addContact: addContact, openPeer: { peer in
let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, emptyResults: items?.isEmpty ?? false, query: query, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, timeFormat: strongSelf.presentationData.dateTimeFormat, addContact: addContact, openPeer: { peer in
self?.listNode.clearHighlightAnimated(true)
self?.openPeer(peer)
}, contextAction: strongSelf.contextAction)
@ -462,6 +493,27 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
let size = layout.size
let sideInset = layout.safeInsets.left
let visibleHeight = layout.size.height
let bottomInset = layout.insets(options: .input).bottom
let padding: CGFloat = 16.0
let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: size.width - sideInset * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
let emptyAnimationHeight = self.emptyResultsAnimationSize.height
let emptyAnimationSpacing: CGFloat = 8.0
let emptyTextSpacing: CGFloat = 8.0
let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing
let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0)
let textTransition = ContainedViewLayoutTransition.immediate
textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - self.emptyResultsAnimationSize.width) / 2.0, y: emptyAnimationY), size: self.emptyResultsAnimationSize))
textTransition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing), size: emptyTitleSize))
textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize)
if !hadValidLayout {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
@ -488,9 +540,26 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
options.insert(.PreferSynchronousResourceLoading)
let isSearching = transition.isSearching
let emptyResults = transition.emptyResults
let query = transition.query
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
self?.listNode.isHidden = !isSearching
self?.dimNode.isHidden = isSearching
guard let strongSelf = self else {
return
}
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Contacts_Search_NoResultsQueryDescription(query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
if let (layout, navigationBarHeight) = strongSelf.containerViewLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
strongSelf.listNode.isHidden = !isSearching
strongSelf.dimNode.isHidden = isSearching
strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults
strongSelf.emptyResultsTextNode.isHidden = !emptyResults
strongSelf.emptyResultsAnimationNode.visibility = emptyResults
})
}
}

View File

@ -1114,15 +1114,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
var preferredAction = ShareControllerPreferredAction.default
var actionCompletionText: String = ""
if let generalMessageContentKind = generalMessageContentKind {
switch generalMessageContentKind {
case .image, .video:
case .image:
preferredAction = .saveToCameraRoll
actionCompletionText = strongSelf.presentationData.strings.Gallery_ImageSaved
case .video:
preferredAction = .saveToCameraRoll
actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved
default:
break
}
} else if messageContentKinds.count == 2 && messageContentKinds.contains(.image) && messageContentKinds.contains(.video) {
preferredAction = .saveToCameraRoll
actionCompletionText = strongSelf.presentationData.strings.Gallery_ImagesAndVideosSaved
}
if messages.count == 1 {
@ -1178,6 +1184,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
shareController.dismissed = { [weak self] _ in
self?.interacting?(false)
}
shareController.actionCompleted = { [weak self] in
if let strongSelf = self {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: actionCompletionText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
}
shareController.completed = { [weak self] peerIds in
if let strongSelf = self {
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in

View File

@ -882,7 +882,7 @@ public final class ShareController: ViewController {
} else {
context = self.sharedContext.makeTempAccountContext(account: self.currentAccount)
}
self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: .standalone(media: media)) |> map(Optional.init))
self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: .standalone(media: media)) |> map(Optional.init), dismissImmediately: true)
}
private func saveToCameraRoll(mediaReference: AnyMediaReference) {

View File

@ -925,8 +925,21 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}))
}
func transitionToProgressWithValue(signal: Signal<Float?, NoError>) {
func transitionToProgressWithValue(signal: Signal<Float?, NoError>, dismissImmediately: Bool = false) {
self.inputFieldNode.deactivateInput()
if dismissImmediately {
self.animateOut(shared: true, completion: {})
self.shareDisposable.set((signal
|> deliverOnMainQueue).start(next: { _ in
}, completed: { [weak self] in
if let strongSelf = self {
strongSelf.dismiss?(true)
}
}))
} else {
let transition = ContainedViewLayoutTransition.animated(duration: 0.12, curve: .easeInOut)
transition.updateAlpha(node: self.actionButtonNode, alpha: 0.0)
transition.updateAlpha(node: self.inputFieldNode, alpha: 0.0)
@ -967,4 +980,5 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}))
}
}
}

View File

@ -802,6 +802,7 @@ public final class VoiceChatController: ViewController {
private var scheduleButtonTitle = ""
private let titleNode: VoiceChatTitleNode
private let participantsNode: VoiceChatTimerNode
private var enqueuedTransitions: [ListTransition] = []
private var enqueuedFullscreenTransitions: [ListTransition] = []
@ -828,6 +829,7 @@ public final class VoiceChatController: ViewController {
private var currentSubtitle: String = ""
private var currentSpeakingSubtitle: String?
private var currentCallMembers: ([GroupCallParticipantsContext.Participant], String?)?
private var currentTotalCount: Int32 = 0
private var currentInvitedPeers: [Peer]?
private var currentSpeakingPeers: Set<PeerId>?
private var currentContentOffset: CGFloat?
@ -1132,6 +1134,8 @@ public final class VoiceChatController: ViewController {
self.timerNode = VoiceChatTimerNode(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)
self.timerNode.isHidden = true
self.participantsNode = VoiceChatTimerNode(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)
super.init()
let context = self.context
@ -1847,6 +1851,7 @@ public final class VoiceChatController: ViewController {
self.listContainer.addSubnode(self.topCornersNode)
self.contentContainer.addSubnode(self.bottomGradientNode)
self.contentContainer.addSubnode(self.bottomPanelBackgroundNode)
self.contentContainer.addSubnode(self.participantsNode)
self.contentContainer.addSubnode(self.tileGridNode)
self.contentContainer.addSubnode(self.mainStageContainerNode)
self.contentContainer.addSubnode(self.transitionContainerNode)
@ -1935,7 +1940,10 @@ public final class VoiceChatController: ViewController {
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: (callMembers?.participants ?? [], callMembers?.loadMoreToken), invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? [])
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0)))
let totalCount = Int32(max(1, callMembers?.totalCount ?? 0))
strongSelf.currentTotalCount = totalCount
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(totalCount)
strongSelf.currentSubtitle = subtitle
if strongSelf.isScheduling {
@ -1979,6 +1987,14 @@ public final class VoiceChatController: ViewController {
let (title, isRecording) = titleAndRecording
if let peer = peerViewMainPeer(view) {
let isLivestream: Bool
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isLivestream = true
} else {
isLivestream = false
}
strongSelf.participantsNode.isHidden = !isLivestream
strongSelf.peer = peer
strongSelf.currentTitleIsCustom = title != nil
strongSelf.currentTitle = title ?? peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
@ -2211,12 +2227,10 @@ public final class VoiceChatController: ViewController {
return
}
if event.joined {
let text: String
if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info {
text = strongSelf.presentationData.strings.LiveStream_PeerJoinedText(event.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
} else {
text = strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
return
}
let text = strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: event.peer, text: text), action: { _ in return false })
}
}))
@ -2651,11 +2665,11 @@ public final class VoiceChatController: ViewController {
}, action: { _, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
guard let strongSelf = self, let peer = strongSelf.peer else {
return
}
let controller = VoiceChatRecordingSetupController(context: strongSelf.context, completion: { [weak self] videoOrientation in
let controller = VoiceChatRecordingSetupController(context: strongSelf.context, peer: peer, completion: { [weak self] videoOrientation in
if let strongSelf = self {
let title: String
let text: String
@ -3958,6 +3972,10 @@ public final class VoiceChatController: ViewController {
transition.animatePositionAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta)
}
}
let participantsFrame = CGRect(x: 0.0, y: bottomCornersFrame.maxY - 100.0, width: size.width, height: 216.0)
transition.updateFrame(node: self.participantsNode, frame: participantsFrame)
self.participantsNode.update(size: participantsFrame.size, participants: self.currentTotalCount, groupingSeparator: self.presentationData.dateTimeFormat.groupingSeparator, transition: .immediate)
}
private var decorationsAreDark: Bool?
@ -5002,6 +5020,15 @@ public final class VoiceChatController: ViewController {
displayPanelVideos = self.displayPanelVideos
}
let isLivestream: Bool
if let channel = self.peer as? TelegramChannel, case .broadcast = channel.info {
isLivestream = true
} else {
isLivestream = false
}
let canManageCall = self.callState?.canManageCall ?? false
var joinedVideo = self.joinedVideo ?? true
var myEntry: VoiceChatPeerEntry?
@ -5014,7 +5041,10 @@ public final class VoiceChatController: ViewController {
let memberState: VoiceChatPeerEntry.State
var memberMuteState: GroupCallParticipantsContext.Participant.MuteState?
if member.hasRaiseHand && !(member.muteState?.canUnmute ?? false) {
if member.hasRaiseHand && !(member.muteState?.canUnmute ?? true) {
if isLivestream && !canManageCall {
continue
}
memberState = .raisedHand
memberMuteState = member.muteState
@ -5050,6 +5080,10 @@ public final class VoiceChatController: ViewController {
disposable.dispose()
self.raisedHandDisplayDisposables[member.peer.id] = nil
}
if isLivestream && !(memberMuteState?.canUnmute ?? true) {
continue
}
}
var memberPeer = member.peer
@ -5080,7 +5114,7 @@ public final class VoiceChatController: ViewController {
effectiveSpeakerVideoEndpointId: self.effectiveSpeaker?.1,
state: memberState,
muteState: memberMuteState,
canManageCall: self.callState?.canManageCall ?? false,
canManageCall: canManageCall,
volume: member.volume,
raisedHand: member.hasRaiseHand,
displayRaisedHandStatus: self.displayedRaisedHands.contains(member.peer.id),

View File

@ -18,14 +18,16 @@ final class VoiceChatRecordingSetupController: ViewController {
}
private let context: AccountContext
private let peer: Peer
private let completion: (Bool?) -> Void
private var animatedIn = false
private var presentationDataDisposable: Disposable?
init(context: AccountContext, completion: @escaping (Bool?) -> Void) {
init(context: AccountContext, peer: Peer, completion: @escaping (Bool?) -> Void) {
self.context = context
self.peer = peer
self.completion = completion
super.init(navigationBarPresentationData: nil)
@ -53,7 +55,7 @@ final class VoiceChatRecordingSetupController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = VoiceChatRecordingSetupControllerNode(controller: self, context: self.context)
self.displayNode = VoiceChatRecordingSetupControllerNode(controller: self, context: self.context, peer: self.peer)
self.controllerNode.completion = { [weak self] videoOrientation in
self?.completion(videoOrientation)
}
@ -147,7 +149,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
init(controller: VoiceChatRecordingSetupController, context: AccountContext) {
init(controller: VoiceChatRecordingSetupController, context: AccountContext, peer: Peer) {
self.controller = controller
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -180,7 +182,14 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
self.contentBackgroundNode = ASDisplayNode()
self.contentBackgroundNode.backgroundColor = backgroundColor
let title = "Record Voice Chat"
let isLivestream: Bool
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
isLivestream = true
} else {
isLivestream = false
}
let title = isLivestream ? self.presentationData.strings.LiveStream_RecordTitle : self.presentationData.strings.VoiceChat_RecordTitle
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor)
@ -200,14 +209,14 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
self.videoAudioButton = HighlightTrackingButtonNode()
self.videoAudioTitleNode = ImmediateTextNode()
self.videoAudioTitleNode.attributedText = NSAttributedString(string: "Video and Audio", font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left)
self.videoAudioTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.VoiceChat_RecordVideoAndAudio, font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left)
self.videoAudioCheckNode = ASImageNode()
self.videoAudioCheckNode.displaysAsynchronously = false
self.videoAudioCheckNode.image = UIImage(bundleImageName: "Call/Check")
self.audioButton = HighlightTrackingButtonNode()
self.audioTitleNode = ImmediateTextNode()
self.audioTitleNode.attributedText = NSAttributedString(string: "Only Audio", font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left)
self.audioTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.VoiceChat_RecordOnlyAudio, font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left)
self.audioCheckNode = ASImageNode()
self.audioCheckNode.displaysAsynchronously = false
self.audioCheckNode.image = UIImage(bundleImageName: "Call/Check")
@ -217,14 +226,14 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
self.portraitButton.cornerRadius = 11.0
self.portraitIconNode = PreviewIconNode()
self.portraitTitleNode = ImmediateTextNode()
self.portraitTitleNode.attributedText = NSAttributedString(string: "Portrait", font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.portraitTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.VoiceChat_RecordPortrait, font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.landscapeButton = HighlightTrackingButtonNode()
self.landscapeButton.backgroundColor = UIColor(rgb: 0x303032)
self.landscapeButton.cornerRadius = 11.0
self.landscapeIconNode = PreviewIconNode()
self.landscapeTitleNode = ImmediateTextNode()
self.landscapeTitleNode.attributedText = NSAttributedString(string: "Landscape", font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.landscapeTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.VoiceChat_RecordLandscape, font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.selectionNode = ASImageNode()
self.selectionNode.displaysAsynchronously = false
@ -515,8 +524,8 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
transition.updateAlpha(node: self.selectionNode, alpha: buttonsAlpha)
self.portraitTitleNode.attributedText = NSAttributedString(string: "Portrait", font: Font.semibold(15.0), textColor: self.videoMode == .portrait ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.landscapeTitleNode.attributedText = NSAttributedString(string: "Landscape", font: Font.semibold(15.0), textColor: self.videoMode == .landscape ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.portraitTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.VoiceChat_RecordPortrait, font: Font.semibold(15.0), textColor: self.videoMode == .portrait ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
self.landscapeTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.VoiceChat_RecordLandscape, font: Font.semibold(15.0), textColor: self.videoMode == .landscape ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
let buttonWidth = floorToScreenPixels((contentFrame.width - inset * 2.0 - 11.0) / 2.0)
let portraitButtonFrame = CGRect(x: inset, y: 56.0 + itemHeight * 2.0 + 25.0, width: buttonWidth, height: 140.0)
@ -547,7 +556,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
self.selectionNode.frame = landscapeButtonFrame.insetBy(dx: -1.0, dy: -1.0)
}
self.doneButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: .button(text: "Start Recording"), title: "", subtitle: "", dark: false, small: false)
self.doneButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: .button(text: self.presentationData.strings.VoiceChat_RecordStartRecording), title: "", subtitle: "", dark: false, small: false)
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))

View File

@ -119,6 +119,40 @@ final class VoiceChatTimerNode: ASDisplayNode {
}
}
func update(size: CGSize, participants: Int32, groupingSeparator: String, transition: ContainedViewLayoutTransition) {
if self.validLayout == nil {
self.updateAnimations()
}
self.validLayout = size
self.foregroundView.frame = CGRect(origin: CGPoint(), size: size)
self.foregroundGradientLayer.frame = self.foregroundView.bounds
self.maskView.frame = self.foregroundView.bounds
let text: String = presentationStringsFormattedNumber(participants, groupingSeparator)
let subtitle = "listening"
self.titleNode.attributedText = NSAttributedString(string: "", font: Font.with(size: 23.0, design: .round, weight: .semibold, traits: []), textColor: .white)
let titleSize = self.titleNode.updateLayout(size)
self.titleNode.frame = CGRect(x: floor((size.width - titleSize.width) / 2.0), y: 48.0, width: titleSize.width, height: titleSize.height)
self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 68.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
var timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
if timerSize.width > size.width - 32.0 {
self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 60.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
}
self.timerNode.frame = CGRect(x: floor((size.width - timerSize.width) / 2.0), y: 78.0, width: timerSize.width, height: timerSize.height)
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.with(size: 21.0, design: .round, weight: .semibold, traits: []), textColor: .white)
let subtitleSize = self.subtitleNode.updateLayout(size)
self.subtitleNode.frame = CGRect(x: floor((size.width - subtitleSize.width) / 2.0), y: 164.0, width: subtitleSize.width, height: subtitleSize.height)
self.foregroundView.frame = CGRect(origin: CGPoint(), size: size)
}
func update(size: CGSize, scheduleTime: Int32?, transition: ContainedViewLayoutTransition) {
if self.validLayout == nil {
self.updateAnimations()

View File

@ -202,6 +202,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case messageViewsPrivacyTips = 25
case chatSpecificThemeLightPreviewTip = 26
case chatSpecificThemeDarkPreviewTip = 27
case interactiveEmojiSyncTip = 28
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -357,6 +358,10 @@ private struct ApplicationSpecificNoticeKeys {
static func chatForwardOptionsTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatForwardOptionsTip.key)
}
static func interactiveEmojiSyncTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.interactiveEmojiSyncTip.key)
}
}
public struct ApplicationSpecificNotice {
@ -961,6 +966,31 @@ public struct ApplicationSpecificNotice {
}
}
public static func getInteractiveEmojiSyncTip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<(Int32, Int32), NoError> {
return accountManager.transaction { transaction -> (Int32, Int32) in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.interactiveEmojiSyncTip()) as? ApplicationSpecificTimestampAndCounterNotice {
return (value.counter, value.timestamp)
} else {
return (0, 0)
}
}
}
public static func incrementInteractiveEmojiSyncTip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1, timestamp: Int32) -> Signal<Int, NoError> {
return accountManager.transaction { transaction -> Int in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.interactiveEmojiSyncTip()) as? ApplicationSpecificTimestampAndCounterNotice {
currentValue = value.counter
}
let previousValue = currentValue
currentValue += Int32(count)
transaction.setNotice(ApplicationSpecificNoticeKeys.interactiveEmojiSyncTip(), ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp))
return Int(previousValue)
}
}
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
}

View File

@ -329,6 +329,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var chatUnreadMentionCountDisposable: Disposable?
private var peerInputActivitiesDisposable: Disposable?
private var peerInputActivitiesPromise = Promise<[(Peer, PeerInputActivity)]>()
private var interactiveEmojiSyncDisposable = MetaDisposable()
private var recentlyUsedInlineBotsValue: [Peer] = []
private var recentlyUsedInlineBotsDisposable: Disposable?
@ -2883,6 +2886,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.choosingStickerActivityPromise.set(value)
}
}, commitEmojiInteraction: { [weak self] messageId, emoji, interaction, file in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer else {
return
}
strongSelf.context.account.updateLocalInputActivity(peerId: PeerActivitySpace(peerId: messageId.peerId, category: .global), activity: .interactingWithEmoji(emoticon: emoji, messageId: messageId, interaction: interaction), isPresent: true)
let currentTimestamp = Int32(Date().timeIntervalSince1970)
let _ = (ApplicationSpecificNotice.getInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] count, timestamp in
if let strongSelf = self, count < 3 && currentTimestamp > timestamp + 24 * 60 * 60 {
strongSelf.interactiveEmojiSyncDisposable.set(
(strongSelf.peerInputActivitiesPromise.get()
|> filter { activities -> Bool in
var found = false
for (_, activity) in activities {
if case .seeingEmojiInteraction(emoji) = activity {
found = true
break
}
}
return found
}
|> map { _ -> Bool in
return true
}
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(false))).start(next: { [weak self] responded in
if let strongSelf = self {
if !responded {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(peer.compactDisplayTitle).string), elevatedLayout: false, action: { _ in return false }), in: .current)
let _ = ApplicationSpecificNotice.incrementInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start()
}
}
})
)
}
})
}, requestMessageUpdate: { [weak self] id in
if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
@ -4196,6 +4236,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.buttonUnreadCountDisposable?.dispose()
self.chatUnreadMentionCountDisposable?.dispose()
self.peerInputActivitiesDisposable?.dispose()
self.interactiveEmojiSyncDisposable.dispose()
self.recentlyUsedInlineBotsDisposable?.dispose()
self.unpinMessageDisposable?.dispose()
self.inputActivityDisposable?.dispose()
@ -5481,7 +5522,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
dismissAction()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }, completion: { _ in
let _ = (strongSelf.context.engine.peers.reportPeerMessages(messageIds: Array(messageIds), reason: reportReason, message: message)
|> deliverOnMainQueue).start(completed: { [weak self] in
|> deliverOnMainQueue).start(completed: {
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .emoji(name: "PoliceCar", text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
})
})
@ -7454,6 +7495,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
strongSelf.chatTitleView?.inputActivities = (peerId, displayActivities)
strongSelf.peerInputActivitiesPromise.set(.single(activities))
for activity in activities {
if case let .interactingWithEmoji(emoticon, messageId, maybeInteraction) = activity.1, let interaction = maybeInteraction {
var found = false

View File

@ -125,6 +125,7 @@ public final class ChatControllerInteraction {
let isAnimatingMessage: (UInt32) -> Bool
let getMessageTransitionNode: () -> ChatMessageTransitionNode?
let updateChoosingSticker: (Bool) -> Void
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void
@ -221,6 +222,7 @@ public final class ChatControllerInteraction {
isAnimatingMessage: @escaping (UInt32) -> Bool,
getMessageTransitionNode: @escaping () -> ChatMessageTransitionNode?,
updateChoosingSticker: @escaping (Bool) -> Void,
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
requestMessageUpdate: @escaping (MessageId) -> Void,
cancelInteractiveKeyboardGestures: @escaping () -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
@ -303,6 +305,7 @@ public final class ChatControllerInteraction {
self.isAnimatingMessage = isAnimatingMessage
self.getMessageTransitionNode = getMessageTransitionNode
self.updateChoosingSticker = updateChoosingSticker
self.commitEmojiInteraction = commitEmojiInteraction
self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@ -362,6 +365,7 @@ public final class ChatControllerInteraction {
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -85,7 +85,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(results: results.map({ $0.file }), query: query)
return currentPanel
} else {
let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: chatPresentationInterfaceState.renderedPeer?.peerId)
panel.controllerInteraction = controllerInteraction
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results: results.map({ $0.file }), query: query)

View File

@ -783,7 +783,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let _ = (saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(completed: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, type: .success), nil)
controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: isVideo ? presentationData.strings.Gallery_VideoSaved : presentationData.strings.Gallery_ImageSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
})
f(.default)
})))

View File

@ -526,14 +526,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let textEmoji = item.message.text.strippedEmoji
var additionalTextEmoji = textEmoji
let (basicEmoji, fitz) = item.message.text.basicEmoji
if ["💛", "💙", "💚", "💜", "🧡", "🖤"].contains(textEmoji) {
if ["💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍"].contains(textEmoji) {
additionalTextEmoji = "❤️".strippedEmoji
} else if fitz != nil {
additionalTextEmoji = basicEmoji
}
var animationItems: [Int: StickerPackItem]?
if let items = item.associatedData.additionalAnimatedEmojiStickers[item.message.text.strippedEmoji] {
if let items = item.associatedData.additionalAnimatedEmojiStickers[textEmoji] {
animationItems = items
} else if let items = item.associatedData.additionalAnimatedEmojiStickers[additionalTextEmoji] {
animationItems = items
@ -1349,10 +1349,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
private func commitEnqueuedAnimations() {
guard let item = self.item, !self.enqueuedAdditionalAnimations.isEmpty else {
guard let item = self.item, let file = self.emojiFile, !self.enqueuedAdditionalAnimations.isEmpty else {
return
}
let textEmoji = item.message.text.strippedEmoji
let enqueuedAnimations = self.enqueuedAdditionalAnimations
self.enqueuedAdditionalAnimations.removeAll()
@ -1365,8 +1364,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
for (index, timestamp) in enqueuedAnimations {
animations.append(EmojiInteraction.Animation(index: index, timeOffset: Float(max(0.0, timestamp - startTimestamp))))
}
item.context.account.updateLocalInputActivity(peerId: PeerActivitySpace(peerId: item.message.id.peerId, category: .global), activity: .interactingWithEmoji(emoticon: textEmoji, messageId: item.message.id, interaction: EmojiInteraction(animations: animations)), isPresent: true)
item.controllerInteraction.commitEmojiInteraction(item.message.id, item.message.text.strippedEmoji, EmojiInteraction(animations: animations), file)
}
func playEmojiInteraction(_ interaction: EmojiInteraction) {
@ -1423,7 +1421,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let textEmoji = item.message.text.strippedEmoji
var additionalTextEmoji = textEmoji
let (basicEmoji, fitz) = item.message.text.basicEmoji
if ["💛", "💙", "💚", "💜", "🧡", "🖤"].contains(textEmoji) {
if ["💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍"].contains(textEmoji) {
additionalTextEmoji = "❤️".strippedEmoji
} else if fitz != nil {
additionalTextEmoji = basicEmoji
@ -1595,7 +1593,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
let (basicEmoji, fitz) = text.basicEmoji
if ["💛", "💙", "💚", "💜", "🧡", "🖤", "❤️"].contains(textEmoji) {
if ["💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍", "❤️"].contains(textEmoji) {
additionalTextEmoji = "❤️".strippedEmoji
} else if fitz != nil {
additionalTextEmoji = basicEmoji

View File

@ -530,6 +530,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -156,6 +156,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -25,6 +25,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
private let context: AccountContext
private var theme: PresentationTheme
private var strings: PresentationStrings
private let peerId: PeerId?
private let scrollNode: ASScrollNode
private var items: [TelegramMediaFile] = []
@ -36,6 +37,8 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
private var ignoreScrolling: Bool = false
private var animateInOnLayout: Bool = false
private weak var peekController: PeekController?
var previewedStickerItem: StickerPackItem?
var updateBackgroundOffset: ((CGFloat, Bool, ContainedViewLayoutTransition) -> Void)?
@ -53,10 +56,11 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
|> distinctUntilChanged
}
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId?) {
self.context = context
self.theme = theme
self.strings = strings
self.peerId = peerId
self.scrollNode = ASScrollNode()
@ -99,12 +103,36 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() {
var menuItems: [ContextMenuItem] = []
menuItems = [
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), false, false, nil, true, itemNode, itemNode.bounds)
})),
if strongSelf.peerId != strongSelf.context.account.peerId && strongSelf.peerId?.namespace != Namespaces.Peer.SecretChat {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
if let strongSelf = self, let peekController = strongSelf.peekController {
if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), true, false, nil, false, animationNode, animationNode.bounds)
} else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode {
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), true, false, nil, false, imageNode, imageNode.bounds)
}
}
f(.default)
})))
}
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
if let strongSelf = self, let peekController = strongSelf.peekController {
if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), false, true, nil, false, animationNode, animationNode.bounds)
} else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode {
let _ = controllerInteraction.sendSticker(.standalone(media: item.file), false, true, nil, false, imageNode, imageNode.bounds)
}
}
f(.default)
})))
menuItems.append(
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
@ -115,7 +143,10 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
}
}
})),
}))
)
menuItems.append(
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
@ -141,7 +172,8 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
}
}
}
}))]
}))
)
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
return nil
@ -159,6 +191,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
controller.visibilityUpdated = { [weak self] visible in
self?.previewingStickersPromise.set(visible)
}
strongSelf.peekController = controller
strongSelf.getControllerInteraction?()?.presentGlobalOverlayController(controller, nil)
return controller
}
@ -431,7 +464,7 @@ final class InlineReactionSearchPanel: ChatInputContextPanelNode {
private var choosingStickerDisposable: Disposable?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId?) {
self.containerNode = ASDisplayNode()
self.backgroundNode = ASDisplayNode()
@ -472,7 +505,7 @@ final class InlineReactionSearchPanel: ChatInputContextPanelNode {
self.backgroundContainerNode = ASDisplayNode()
self.stickersNode = InlineReactionSearchStickersNode(context: context, theme: theme, strings: strings)
self.stickersNode = InlineReactionSearchStickersNode(context: context, theme: theme, strings: strings, peerId: peerId)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)

View File

@ -148,6 +148,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))

View File

@ -2183,6 +2183,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -453,7 +453,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
let size = forwardAccessoryPanelNode.calculateSizeThatFits(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height))
forwardAccessoryPanelNode.updateState(size: size, inset: layout.safeInsets.left, interfaceState: self.presentationInterfaceState)
forwardAccessoryPanelNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, forwardOptionsState: self.presentationInterfaceState.interfaceState.forwardOptionsState)
let panelFrame = CGRect(x: layout.safeInsets.left, y: layout.size.height - (textPanelHeight ?? 0.0) - size.height, width: size.width - layout.safeInsets.left - layout.safeInsets.right, height: size.height)
let panelFrame = CGRect(x: 0.0, y: layout.size.height - (textPanelHeight ?? 0.0) - size.height, width: size.width, height: size.height)
accessoryHeight = size.height
if forwardAccessoryPanelNode.frame.width.isZero {

View File

@ -1295,6 +1295,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, getMessageTransitionNode: {
return nil
}, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,