mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
950c3d9f1e
commit
bb015f2bfa
@ -116,13 +116,14 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
if case let .search(search) = source {
|
||||
switch search {
|
||||
case .recentPeers:
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
break
|
||||
/*items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
let _ = (context.engine.peers.removeRecentPeer(peerId: peerId)
|
||||
|> deliverOnMainQueue).startStandalone(completed: {
|
||||
f(.default)
|
||||
})
|
||||
})))
|
||||
items.append(.separator)
|
||||
items.append(.separator)*/
|
||||
case .recentSearch:
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId)
|
||||
@ -535,6 +536,23 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
}
|
||||
f(.default)
|
||||
})))
|
||||
} else if case let .search(search) = source {
|
||||
switch search {
|
||||
case .recentPeers, .search:
|
||||
if peerGroup != nil {
|
||||
if !items.isEmpty {
|
||||
items.append(.separator)
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
if let chatListController = chatListController {
|
||||
chatListController.deletePeerChat(peerId: peerId, joined: joined)
|
||||
}
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -544,6 +544,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
}
|
||||
|
||||
private struct ChatListFilterPresetControllerState: Equatable {
|
||||
var isExisting: Bool
|
||||
var name: ChatFolderTitle
|
||||
var changedName: Bool
|
||||
var nameInputMode: ListComposePollOptionComponent.InputMode = .keyboard
|
||||
@ -564,6 +565,10 @@ private struct ChatListFilterPresetControllerState: Equatable {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.isExisting {
|
||||
return true
|
||||
}
|
||||
|
||||
let defaultCategories: ChatListFilterPeerCategories = .all
|
||||
let defaultExcludeArchived = true
|
||||
let defaultExcludeMuted = false
|
||||
@ -1388,6 +1393,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
|
||||
initialName = ChatFolderTitle(text: "", entities: [], enableAnimations: true)
|
||||
}
|
||||
var initialState = ChatListFilterPresetControllerState(
|
||||
isExisting: initialPreset?.id != nil,
|
||||
name: initialName,
|
||||
changedName: initialPreset != nil,
|
||||
color: initialPreset?.data?.color,
|
||||
|
@ -2048,11 +2048,31 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
return result
|
||||
}
|
||||
|
||||
let updatedLocalPeers = context.engine.contacts.searchLocalPeers(query: query.lowercased())
|
||||
|> mapToSignal { peers -> Signal<[EngineRenderedPeer], NoError> in
|
||||
return context.engine.data.subscribe(
|
||||
EngineDataMap(peers.map { peer in
|
||||
return TelegramEngine.EngineData.Item.Messages.ChatListIndex(id: peer.peerId)
|
||||
})
|
||||
)
|
||||
|> map { chatListIndices -> [EngineRenderedPeer] in
|
||||
return peers.filter { peer in
|
||||
if peer.peerId.namespace == Namespaces.Peer.CloudUser || peer.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return true
|
||||
}
|
||||
if let maybeIndex = chatListIndices[peer.peerId], maybeIndex != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundLocalPeers = combineLatest(
|
||||
context.engine.contacts.searchLocalPeers(query: query.lowercased()),
|
||||
updatedLocalPeers,
|
||||
fixedOrRemovedRecentlySearchedPeers
|
||||
)
|
||||
|> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer], Set<EnginePeer.Id>, EngineGlobalNotificationSettings), NoError> in
|
||||
|> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: TelegramEngine.EngineData.Item.Messages.PeerUnreadState.Result], [EngineRenderedPeer], Set<EnginePeer.Id>, EngineGlobalNotificationSettings), NoError> in
|
||||
let recentlySearched = allRecentlySearched.filter { peer in
|
||||
guard let peer = peer.peer.peer else {
|
||||
return false
|
||||
@ -2083,8 +2103,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
),
|
||||
EngineDataMap(
|
||||
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in
|
||||
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId)
|
||||
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadState in
|
||||
return TelegramEngine.EngineData.Item.Messages.PeerUnreadState(id: peerId)
|
||||
}
|
||||
),
|
||||
TelegramEngine.EngineData.Item.NotificationSettings.Global()
|
||||
@ -2118,8 +2138,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}
|
||||
let unreadCount = unreadCounts[peer.peerId]
|
||||
if let unreadCount = unreadCount, unreadCount > 0 {
|
||||
unread[peer.peerId] = (Int32(unreadCount), isMuted)
|
||||
if let unreadCount = unreadCount, (unreadCount.count > 0 || unreadCount.isMarkedUnread) {
|
||||
unread[peer.peerId] = (Int32(unreadCount.count), isMuted)
|
||||
}
|
||||
}
|
||||
return (peers: peers, unread: unread, recentlySearchedPeerIds: recentlySearchedPeerIds)
|
||||
|
@ -955,6 +955,33 @@ public struct ComponentTransition {
|
||||
}
|
||||
}
|
||||
|
||||
public func setShadowPath(layer: CALayer, path: CGPath, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.shadowPath = path
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
if let previousPath = layer.shadowPath, previousPath != path {
|
||||
layer.animate(
|
||||
from: previousPath,
|
||||
to: path,
|
||||
keyPath: "shadowPath",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
curve: curve,
|
||||
removeOnCompletion: true,
|
||||
additive: false,
|
||||
completion: completion
|
||||
)
|
||||
layer.shadowPath = path
|
||||
} else {
|
||||
layer.shadowPath = path
|
||||
completion?(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func setShapeLayerPath(layer: CAShapeLayer, path: CGPath, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
|
@ -37,7 +37,7 @@ public final class BlurredBackgroundComponent: Component {
|
||||
private var vibrancyEffectView: UIVisualEffectView?
|
||||
|
||||
public func update(component: BlurredBackgroundComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
self.updateColor(color: component.color, transition: transition.containedViewLayoutTransition)
|
||||
self.updateColor(color: component.color, forceKeepBlur: true, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
self.update(size: availableSize, cornerRadius: component.cornerRadius, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
|
@ -208,6 +208,20 @@ public final class UnreadMessageCountsView: PostboxView {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func countOrUnread(for item: UnreadMessageCountsItem) -> (Int32, Bool)? {
|
||||
for entry in self.entries {
|
||||
switch entry {
|
||||
case .total, .totalInGroup:
|
||||
break
|
||||
case let .peer(peerId, state):
|
||||
if case .peer(peerId, _) = item {
|
||||
return (state?.count ?? 0, state?.markedUnread ?? false)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
final class MutableCombinedReadStateView: MutablePostboxView {
|
||||
|
@ -116,6 +116,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||
"//submodules/TelegramUI/Components/BackButtonComponent",
|
||||
"//submodules/TelegramUI/Components/AlertComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/DirectMediaImageCache",
|
||||
"//submodules/FastBlur",
|
||||
],
|
||||
|
@ -0,0 +1,377 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
final class VideoChatEncryptionKeyComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let emoji: [String]
|
||||
let isExpanded: Bool
|
||||
let tapAction: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
emoji: [String],
|
||||
isExpanded: Bool,
|
||||
tapAction: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.emoji = emoji
|
||||
self.isExpanded = isExpanded
|
||||
self.tapAction = tapAction
|
||||
}
|
||||
|
||||
static func ==(lhs: VideoChatEncryptionKeyComponent, rhs: VideoChatEncryptionKeyComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.emoji != rhs.emoji {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let containerView: UIView
|
||||
private var emojiItems: [ComponentView<Empty>] = []
|
||||
private let background = ComponentView<Empty>()
|
||||
private let backgroundShadowLayer = SimpleLayer()
|
||||
private let collapsedText = ComponentView<Empty>()
|
||||
private let expandedText = ComponentView<Empty>()
|
||||
private let expandedSeparatorLayer = SimpleLayer()
|
||||
private let expandedButtonText = ComponentView<Empty>()
|
||||
|
||||
private var component: VideoChatEncryptionKeyComponent?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.containerView = UIView()
|
||||
self.containerView.clipsToBounds = true
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.containerView)
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
component.tapAction()
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.containerView.frame.contains(point) {
|
||||
return self
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: VideoChatEncryptionKeyComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
let alphaTransition: ComponentTransition
|
||||
if transition.animation.isImmediate {
|
||||
alphaTransition = .immediate
|
||||
} else {
|
||||
alphaTransition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
|
||||
let collapsedSideInset: CGFloat = 7.0
|
||||
let collapsedVerticalInset: CGFloat = 8.0
|
||||
let collapsedEmojiSpacing: CGFloat = 0.0
|
||||
let collapsedEmojiTextSpacing: CGFloat = 5.0
|
||||
|
||||
let expandedTopInset: CGFloat = 8.0
|
||||
let expandedSideInset: CGFloat = 14.0
|
||||
var expandedEmojiSpacing: CGFloat = 10.0
|
||||
let expandedEmojiTextSpacing: CGFloat = 10.0
|
||||
let expandedTextButtonSpacing: CGFloat = 10.0
|
||||
let expandedButtonTopInset: CGFloat = 12.0
|
||||
let expandedButtonBottomInset: CGFloat = 13.0
|
||||
|
||||
let emojiItemSizes = (0 ..< component.emoji.count).map { i -> CGSize in
|
||||
let emojiItem: ComponentView<Empty>
|
||||
if self.emojiItems.count > i {
|
||||
emojiItem = self.emojiItems[i]
|
||||
} else {
|
||||
emojiItem = ComponentView()
|
||||
self.emojiItems.append(emojiItem)
|
||||
}
|
||||
return emojiItem.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.emoji[i], font: Font.regular(40.0), textColor: .white))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let collapsedTextSize = self.collapsedText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "End-to-end encrypted", font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 1000.0, height: 1000.0)
|
||||
)
|
||||
|
||||
let expandedTextSize = self.expandedText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(string: "These four emojis represent the call's encryption key. They must match for all participants and change when someone joins or leaves.", font: Font.regular(12.0), textColor: component.theme.list.itemPrimaryTextColor)),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.3
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - expandedSideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
|
||||
let expandedButtonTextSize = self.expandedButtonText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "Close", font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - expandedSideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
|
||||
let collapsedEmojiFactor: CGFloat = 20.0 / 40.0
|
||||
let collapsedEmojiSizes = emojiItemSizes.map { CGSize(width: floor($0.width * collapsedEmojiFactor), height: floor($0.height * collapsedEmojiFactor)) }
|
||||
|
||||
let collapsedSize = CGSize(width: collapsedTextSize.width + collapsedSideInset * 2.0 + collapsedEmojiTextSpacing * 2.0 + collapsedEmojiSpacing + collapsedEmojiSizes.reduce(into: 0.0, { $0 += $1.width }), height: collapsedTextSize.height + collapsedVerticalInset * 2.0)
|
||||
|
||||
var expandedEmojiWidth = expandedEmojiSpacing * CGFloat(self.emojiItems.count - 1) + emojiItemSizes.reduce(into: 0.0, { $0 += $1.width })
|
||||
|
||||
var expandedSize = CGSize(width: expandedSideInset * 2.0, height: 0.0)
|
||||
|
||||
expandedSize.width += max(expandedTextSize.width, expandedEmojiWidth)
|
||||
|
||||
expandedSize.height += expandedTopInset
|
||||
expandedSize.height += emojiItemSizes[0].height
|
||||
expandedSize.height += expandedEmojiTextSpacing
|
||||
expandedSize.height += expandedTextSize.height
|
||||
expandedSize.height += expandedTextButtonSpacing
|
||||
expandedSize.height += expandedButtonTopInset
|
||||
expandedSize.height += expandedButtonTextSize.height
|
||||
expandedSize.height += expandedButtonBottomInset
|
||||
|
||||
if expandedEmojiWidth < expandedSize.width - expandedSideInset * 2.0 {
|
||||
expandedEmojiWidth = expandedSize.width - expandedSideInset * 2.0
|
||||
|
||||
let cleanEmojiWidth = emojiItemSizes.reduce(into: 0.0, { $0 += $1.width })
|
||||
expandedEmojiSpacing = floorToScreenPixels((expandedEmojiWidth - cleanEmojiWidth) / CGFloat(self.emojiItems.count - 1))
|
||||
expandedEmojiSpacing = min(expandedEmojiSpacing, 24.0)
|
||||
expandedEmojiWidth = expandedEmojiSpacing * CGFloat(self.emojiItems.count - 1) + emojiItemSizes.reduce(into: 0.0, { $0 += $1.width })
|
||||
}
|
||||
|
||||
let backgroundSize = component.isExpanded ? expandedSize : collapsedSize
|
||||
let backgroundCornerRadius: CGFloat = component.isExpanded ? 10.0 : collapsedSize.height * 0.5
|
||||
|
||||
let _ = self.background.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(FilledRoundedRectangleComponent(
|
||||
color: component.theme.list.itemBlocksBackgroundColor,
|
||||
cornerRadius: .value(backgroundCornerRadius), smoothCorners: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: backgroundSize
|
||||
)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize)
|
||||
|
||||
if self.backgroundShadowLayer.superlayer == nil {
|
||||
self.backgroundShadowLayer.backgroundColor = UIColor.clear.cgColor
|
||||
self.containerView.layer.addSublayer(self.backgroundShadowLayer)
|
||||
}
|
||||
self.backgroundShadowLayer.shadowOpacity = 0.3
|
||||
self.backgroundShadowLayer.shadowColor = UIColor.black.cgColor
|
||||
self.backgroundShadowLayer.shadowRadius = 5.0
|
||||
self.backgroundShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
alphaTransition.setAlpha(layer: self.backgroundShadowLayer, alpha: component.isExpanded ? 1.0 : 0.0)
|
||||
|
||||
transition.setFrame(layer: self.backgroundShadowLayer, frame: backgroundFrame)
|
||||
transition.setCornerRadius(layer: self.backgroundShadowLayer, cornerRadius: backgroundCornerRadius)
|
||||
transition.setShadowPath(layer: self.backgroundShadowLayer, path: UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: backgroundFrame.size), cornerRadius: backgroundCornerRadius).cgPath)
|
||||
|
||||
if let backgroundView = self.background.view {
|
||||
if backgroundView.superview == nil {
|
||||
self.containerView.addSubview(backgroundView)
|
||||
}
|
||||
transition.setFrame(view: backgroundView, frame: backgroundFrame)
|
||||
}
|
||||
|
||||
var collapsedEmojiLeftOffset = collapsedSideInset
|
||||
var collapsedEmojiRightOffset = collapsedSize.width - collapsedSideInset
|
||||
|
||||
for i in 0 ..< self.emojiItems.count {
|
||||
let mappedIndex: Int
|
||||
if i < 2 {
|
||||
mappedIndex = i
|
||||
} else {
|
||||
mappedIndex = self.emojiItems.count - (i - 1)
|
||||
}
|
||||
|
||||
var collapsedItemFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((collapsedSize.height - collapsedEmojiSizes[mappedIndex].height) * 0.5)), size: collapsedEmojiSizes[mappedIndex])
|
||||
if i < 2 {
|
||||
if mappedIndex != 0 {
|
||||
collapsedEmojiLeftOffset += collapsedEmojiSpacing
|
||||
}
|
||||
collapsedItemFrame.origin.x = collapsedEmojiLeftOffset
|
||||
collapsedEmojiLeftOffset += collapsedEmojiSizes[mappedIndex].width
|
||||
} else {
|
||||
if mappedIndex != 0 {
|
||||
collapsedEmojiRightOffset -= collapsedEmojiSpacing
|
||||
}
|
||||
collapsedItemFrame.origin.x = collapsedEmojiRightOffset - collapsedEmojiSizes[mappedIndex].width
|
||||
collapsedEmojiRightOffset -= collapsedEmojiSizes[mappedIndex].width
|
||||
}
|
||||
|
||||
var expandedItemFrame = CGRect(origin: CGPoint(x: floor((backgroundFrame.width - expandedEmojiWidth) * 0.5), y: expandedTopInset), size: emojiItemSizes[mappedIndex])
|
||||
expandedItemFrame.origin.x += CGFloat(mappedIndex) * (emojiItemSizes[0].width + expandedEmojiSpacing)
|
||||
|
||||
let itemFrame: CGRect
|
||||
if component.isExpanded {
|
||||
itemFrame = expandedItemFrame
|
||||
} else {
|
||||
itemFrame = collapsedItemFrame
|
||||
}
|
||||
|
||||
if let itemView = self.emojiItems[mappedIndex].view {
|
||||
if itemView.superview == nil {
|
||||
self.containerView.addSubview(itemView)
|
||||
}
|
||||
transition.setPosition(view: itemView, position: itemFrame.center)
|
||||
itemView.bounds = CGRect(origin: CGPoint(), size: emojiItemSizes[mappedIndex])
|
||||
transition.setScale(view: itemView, scale: itemFrame.height / emojiItemSizes[mappedIndex].height)
|
||||
}
|
||||
}
|
||||
|
||||
var collapsedTextFrame = CGRect(origin: CGPoint(x: collapsedEmojiLeftOffset + collapsedEmojiTextSpacing, y: floor((collapsedSize.height - collapsedTextSize.height) * 0.5)), size: collapsedTextSize)
|
||||
var expandedTextFrame = CGRect(origin: CGPoint(x: floor((backgroundSize.width - expandedTextSize.width) * 0.5), y: expandedTopInset + emojiItemSizes[0].height + expandedEmojiTextSpacing), size: expandedTextSize)
|
||||
|
||||
if component.isExpanded {
|
||||
collapsedTextFrame.origin = expandedTextFrame.origin
|
||||
} else {
|
||||
expandedTextFrame.origin = collapsedTextFrame.origin
|
||||
}
|
||||
|
||||
if let collapsedTextView = self.collapsedText.view {
|
||||
if collapsedTextView.superview == nil {
|
||||
collapsedTextView.layer.anchorPoint = CGPoint()
|
||||
self.containerView.addSubview(collapsedTextView)
|
||||
} else {
|
||||
if collapsedTextView.alpha != (component.isExpanded ? 0.0 : 1.0) {
|
||||
if let blurFilter = CALayer.blur() {
|
||||
let maxBlur: CGFloat = 8.0
|
||||
if !component.isExpanded {
|
||||
blurFilter.setValue(0.0 as NSNumber, forKey: "inputRadius")
|
||||
collapsedTextView.layer.filters = [blurFilter]
|
||||
collapsedTextView.layer.animate(from: maxBlur as NSNumber, to: 0.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak collapsedTextView] flag in
|
||||
if flag, let collapsedTextView {
|
||||
collapsedTextView.layer.filters = nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
blurFilter.setValue(maxBlur as NSNumber, forKey: "inputRadius")
|
||||
collapsedTextView.layer.filters = [blurFilter]
|
||||
collapsedTextView.layer.animate(from: 0.0 as NSNumber, to: maxBlur as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transition.setPosition(view: collapsedTextView, position: collapsedTextFrame.origin)
|
||||
collapsedTextView.bounds = CGRect(origin: CGPoint(), size: collapsedTextFrame.size)
|
||||
alphaTransition.setAlpha(view: collapsedTextView, alpha: component.isExpanded ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
if let expandedTextView = self.expandedText.view {
|
||||
if expandedTextView.superview == nil {
|
||||
expandedTextView.layer.anchorPoint = CGPoint()
|
||||
self.containerView.addSubview(expandedTextView)
|
||||
} else {
|
||||
if expandedTextView.alpha != (component.isExpanded ? 1.0 : 0.0) {
|
||||
if let blurFilter = CALayer.blur() {
|
||||
let maxBlur: CGFloat = 8.0
|
||||
if component.isExpanded {
|
||||
blurFilter.setValue(0.0 as NSNumber, forKey: "inputRadius")
|
||||
expandedTextView.layer.filters = [blurFilter]
|
||||
expandedTextView.layer.animate(from: maxBlur as NSNumber, to: 0.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak expandedTextView] flag in
|
||||
if flag, let expandedTextView {
|
||||
expandedTextView.layer.filters = nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
blurFilter.setValue(maxBlur as NSNumber, forKey: "inputRadius")
|
||||
expandedTextView.layer.filters = [blurFilter]
|
||||
expandedTextView.layer.animate(from: 0.0 as NSNumber, to: maxBlur as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transition.setPosition(view: expandedTextView, position: expandedTextFrame.origin)
|
||||
expandedTextView.bounds = CGRect(origin: CGPoint(), size: expandedTextFrame.size)
|
||||
alphaTransition.setAlpha(view: expandedTextView, alpha: component.isExpanded ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
let expandedButtonOffset: CGFloat = component.isExpanded ? 0.0 : (expandedButtonBottomInset + expandedButtonTextSize.height + expandedButtonTopInset)
|
||||
|
||||
if self.expandedSeparatorLayer.superlayer == nil {
|
||||
self.containerView.layer.addSublayer(self.expandedSeparatorLayer)
|
||||
}
|
||||
self.expandedSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
transition.setFrame(layer: self.expandedSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: backgroundSize.height - expandedButtonBottomInset - expandedButtonTextSize.height - expandedButtonTopInset + expandedButtonOffset), size: CGSize(width: backgroundSize.width, height: UIScreenPixel)))
|
||||
alphaTransition.setAlpha(layer: self.expandedSeparatorLayer, alpha: component.isExpanded ? 1.0 : 0.0)
|
||||
|
||||
if let expandedButtonTextView = self.expandedButtonText.view {
|
||||
if expandedButtonTextView.superview == nil {
|
||||
self.containerView.addSubview(expandedButtonTextView)
|
||||
}
|
||||
transition.setFrame(view: expandedButtonTextView, frame: CGRect(origin: CGPoint(x: floor((backgroundSize.width - expandedButtonTextSize.width) * 0.5), y: backgroundSize.height - expandedButtonBottomInset - expandedButtonTextSize.height + expandedButtonOffset), size: expandedButtonTextSize))
|
||||
alphaTransition.setAlpha(view: expandedButtonTextView, alpha: component.isExpanded ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize))
|
||||
|
||||
return CGSize(width: backgroundSize.width, height: collapsedSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import AvatarNode
|
||||
import TelegramAudio
|
||||
import LegacyComponents
|
||||
import TooltipUI
|
||||
import BlurredBackgroundComponent
|
||||
|
||||
extension VideoChatCall {
|
||||
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
||||
@ -219,6 +220,9 @@ final class VideoChatScreenComponent: Component {
|
||||
let navigationLeftButton = ComponentView<Empty>()
|
||||
let navigationRightButton = ComponentView<Empty>()
|
||||
var navigationSidebarButton: ComponentView<Empty>?
|
||||
var encryptionKeyBackground: ComponentView<Empty>?
|
||||
var encryptionKey: ComponentView<Empty>?
|
||||
var isEncryptionKeyExpanded: Bool = false
|
||||
|
||||
let videoButton = ComponentView<Empty>()
|
||||
let leaveButton = ComponentView<Empty>()
|
||||
@ -406,6 +410,12 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let encryptionKeyBackgroundView = self.encryptionKeyBackground?.view, let _ = encryptionKeyBackgroundView.hitTest(self.convert(point, to: encryptionKeyBackgroundView), with: event) {
|
||||
if let encryptionKeyView = self.encryptionKey?.view {
|
||||
return encryptionKeyView
|
||||
}
|
||||
}
|
||||
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
@ -1744,7 +1754,7 @@ final class VideoChatScreenComponent: Component {
|
||||
|
||||
let topInset: CGFloat = environment.statusBarHeight + 2.0
|
||||
let navigationBarHeight: CGFloat = 61.0
|
||||
let navigationHeight = topInset + navigationBarHeight
|
||||
var navigationHeight = topInset + navigationBarHeight
|
||||
|
||||
let navigationButtonAreaWidth: CGFloat = 40.0
|
||||
let navigationButtonDiameter: CGFloat = 28.0
|
||||
@ -1950,6 +1960,52 @@ final class VideoChatScreenComponent: Component {
|
||||
alphaTransition.setAlpha(view: titleView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
var encryptionKeyFrame: CGRect?
|
||||
if component.initialCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
||||
navigationHeight -= 2.0
|
||||
let encryptionKey: ComponentView<Empty>
|
||||
var encryptionKeyTransition = transition
|
||||
if let current = self.encryptionKey {
|
||||
encryptionKey = current
|
||||
} else {
|
||||
encryptionKeyTransition = encryptionKeyTransition.withAnimation(.none)
|
||||
encryptionKey = ComponentView()
|
||||
self.encryptionKey = encryptionKey
|
||||
}
|
||||
|
||||
let encryptionKeySize = encryptionKey.update(
|
||||
transition: encryptionKeyTransition,
|
||||
component: AnyComponent(VideoChatEncryptionKeyComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
emoji: ["👌", "🧡", "🌹", "🤷"],
|
||||
isExpanded: self.isEncryptionKeyExpanded,
|
||||
tapAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isEncryptionKeyExpanded = !self.isEncryptionKeyExpanded
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: min(400.0, availableSize.width - sideInset * 2.0 - 16.0 * 2.0), height: 10000.0)
|
||||
)
|
||||
let encryptionKeyFrameValue = CGRect(origin: CGPoint(x: floor((availableSize.width - encryptionKeySize.width) * 0.5), y: navigationHeight), size: encryptionKeySize)
|
||||
encryptionKeyFrame = encryptionKeyFrameValue
|
||||
|
||||
navigationHeight += encryptionKeySize.height
|
||||
navigationHeight += 16.0
|
||||
} else if let encryptionKey = self.encryptionKey {
|
||||
self.encryptionKey = nil
|
||||
encryptionKey.view?.removeFromSuperview()
|
||||
|
||||
self.encryptionKeyBackground?.view?.removeFromSuperview()
|
||||
self.encryptionKeyBackground = nil
|
||||
}
|
||||
|
||||
let areButtonsCollapsed: Bool
|
||||
let mainColumnWidth: CGFloat
|
||||
let mainColumnSideInset: CGFloat
|
||||
@ -2226,6 +2282,51 @@ final class VideoChatScreenComponent: Component {
|
||||
alphaTransition.setAlpha(view: participantsView, alpha: participantsAlpha)
|
||||
}
|
||||
|
||||
if let encryptionKeyView = self.encryptionKey?.view, let encryptionKeyFrame {
|
||||
if encryptionKeyView.superview == nil {
|
||||
self.containerView.addSubview(encryptionKeyView)
|
||||
}
|
||||
transition.setFrame(view: encryptionKeyView, frame: encryptionKeyFrame)
|
||||
alphaTransition.setAlpha(view: encryptionKeyView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||
|
||||
if self.isEncryptionKeyExpanded {
|
||||
let encryptionKeyBackground: ComponentView<Empty>
|
||||
var encryptionKeyBackgroundTransition = transition
|
||||
if let current = self.encryptionKeyBackground {
|
||||
encryptionKeyBackground = current
|
||||
} else {
|
||||
encryptionKeyBackgroundTransition = encryptionKeyBackgroundTransition.withAnimation(.none)
|
||||
encryptionKeyBackground = ComponentView()
|
||||
self.encryptionKeyBackground = encryptionKeyBackground
|
||||
}
|
||||
let _ = encryptionKeyBackground.update(
|
||||
transition: encryptionKeyBackgroundTransition,
|
||||
component: AnyComponent(BlurredBackgroundComponent(
|
||||
color: .clear,
|
||||
tintContainerView: nil,
|
||||
cornerRadius: 0.0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let encryptionKeyBackgroundView = encryptionKeyBackground.view {
|
||||
if encryptionKeyBackgroundView.superview == nil {
|
||||
self.containerView.insertSubview(encryptionKeyBackgroundView, belowSubview: encryptionKeyView)
|
||||
encryptionKeyBackgroundView.alpha = 0.0
|
||||
}
|
||||
alphaTransition.setAlpha(view: encryptionKeyBackgroundView, alpha: 1.0)
|
||||
encryptionKeyBackgroundTransition.setFrame(view: encryptionKeyBackgroundView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
}
|
||||
} else if let encryptionKeyBackground = self.encryptionKeyBackground {
|
||||
self.encryptionKeyBackground = nil
|
||||
if let encryptionKeyBackgroundView = encryptionKeyBackground.view {
|
||||
alphaTransition.setAlpha(view: encryptionKeyBackgroundView, alpha: 0.0, completion: { [weak encryptionKeyBackgroundView] _ in
|
||||
encryptionKeyBackgroundView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let callState = self.callState, let scheduleTimestamp = callState.scheduleTimestamp {
|
||||
let scheduleInfo: ComponentView<Empty>
|
||||
var scheduleInfoTransition = transition
|
||||
|
@ -227,6 +227,42 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
|
||||
public struct PeerUnreadState: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public struct Result: Equatable {
|
||||
public var count: Int
|
||||
public var isMarkedUnread: Bool
|
||||
|
||||
public init(count: Int, isMarkedUnread: Bool) {
|
||||
self.count = count
|
||||
self.isMarkedUnread = isMarkedUnread
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .unreadCounts(items: [.peer(id: self.id, handleThreads: true)])
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? UnreadMessageCountsView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
if let (value, isUnread) = view.countOrUnread(for: .peer(id: self.id, handleThreads: true)) {
|
||||
return Result(count: Int(value), isMarkedUnread: isUnread)
|
||||
}
|
||||
return Result(count: 0, isMarkedUnread: false)
|
||||
}
|
||||
}
|
||||
|
||||
public struct TotalReadCounters: TelegramEngineDataItem, PostboxViewDataItem {
|
||||
public typealias Result = EngineTotalReadCounters
|
||||
|
||||
|
@ -17,7 +17,7 @@ public enum CreateChannelMode {
|
||||
case supergroup(isForum: Bool)
|
||||
}
|
||||
|
||||
private func createChannel(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, mode: CreateChannelMode, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal<PeerId, CreateChannelError> {
|
||||
private func createChannel(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, mode: CreateChannelMode, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32?) -> Signal<PeerId, CreateChannelError> {
|
||||
return postbox.transaction { transaction -> Signal<PeerId, CreateChannelError> in
|
||||
var flags: Int32 = 0
|
||||
switch mode {
|
||||
@ -43,7 +43,11 @@ private func createChannel(postbox: Postbox, network: Network, stateManager: Acc
|
||||
|
||||
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers)
|
||||
|
||||
return network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address, ttlPeriod: nil), automaticFloodWait: false)
|
||||
if ttlPeriod != nil {
|
||||
flags |= (1 << 4)
|
||||
}
|
||||
|
||||
return network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address, ttlPeriod: ttlPeriod), automaticFloodWait: false)
|
||||
|> mapError { error -> CreateChannelError in
|
||||
if error.errorCode == 406 {
|
||||
return .serverProvided(error.errorDescription)
|
||||
@ -91,11 +95,11 @@ private func createChannel(postbox: Postbox, network: Network, stateManager: Acc
|
||||
}
|
||||
|
||||
func _internal_createChannel(account: Account, title: String, description: String?, username: String?) -> Signal<PeerId, CreateChannelError> {
|
||||
return createChannel(postbox: account.postbox, network: account.network, stateManager: account.stateManager, title: title, description: description, username: nil, mode: .channel)
|
||||
return createChannel(postbox: account.postbox, network: account.network, stateManager: account.stateManager, title: title, description: description, username: nil, mode: .channel, ttlPeriod: nil)
|
||||
}
|
||||
|
||||
public func _internal_createSupergroup(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, isForum: Bool, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal<PeerId, CreateChannelError> {
|
||||
return createChannel(postbox: postbox, network: network, stateManager: stateManager, title: title, description: description, username: username, mode: .supergroup(isForum: isForum), location: location, isForHistoryImport: isForHistoryImport)
|
||||
public func _internal_createSupergroup(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, isForum: Bool, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32? = nil) -> Signal<PeerId, CreateChannelError> {
|
||||
return createChannel(postbox: postbox, network: network, stateManager: stateManager, title: title, description: description, username: username, mode: .supergroup(isForum: isForum), location: location, isForHistoryImport: isForHistoryImport, ttlPeriod: ttlPeriod)
|
||||
}
|
||||
|
||||
public enum DeleteChannelError {
|
||||
|
@ -466,8 +466,8 @@ public extension TelegramEngine {
|
||||
return _internal_createChannel(account: self.account, title: title, description: description, username: username)
|
||||
}
|
||||
|
||||
public func createSupergroup(title: String, description: String?, username: String? = nil, isForum: Bool = false, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal<PeerId, CreateChannelError> {
|
||||
return _internal_createSupergroup(postbox: self.account.postbox, network: self.account.network, stateManager: account.stateManager, title: title, description: description, username: username, isForum: isForum, location: location, isForHistoryImport: isForHistoryImport)
|
||||
public func createSupergroup(title: String, description: String?, username: String? = nil, isForum: Bool = false, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32? = nil) -> Signal<PeerId, CreateChannelError> {
|
||||
return _internal_createSupergroup(postbox: self.account.postbox, network: self.account.network, stateManager: account.stateManager, title: title, description: description, username: username, isForum: isForum, location: location, isForHistoryImport: isForHistoryImport, ttlPeriod: ttlPeriod)
|
||||
}
|
||||
|
||||
public func deleteChannel(peerId: PeerId) -> Signal<Void, DeleteChannelError> {
|
||||
|
@ -467,8 +467,9 @@ public final class MessageInputPanelComponent: Component {
|
||||
private let gradientView: UIImageView
|
||||
private let bottomGradientView: UIView
|
||||
|
||||
private let placeholder = ComponentView<Empty>()
|
||||
private let vibrancyPlaceholder = ComponentView<Empty>()
|
||||
private var currentPlaceholderType: Bool?
|
||||
private var placeholder = ComponentView<Empty>()
|
||||
private var vibrancyPlaceholder = ComponentView<Empty>()
|
||||
|
||||
private let counter = ComponentView<Empty>()
|
||||
private var header: ComponentView<Empty>?
|
||||
@ -868,7 +869,17 @@ public final class MessageInputPanelComponent: Component {
|
||||
|
||||
let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate
|
||||
let placeholderSize: CGSize
|
||||
|
||||
if case let .plain(string) = component.placeholder, string.contains("#") {
|
||||
let placeholderType = false
|
||||
if let currentPlaceholderType = self.currentPlaceholderType, currentPlaceholderType != placeholderType {
|
||||
self.placeholder.view?.removeFromSuperview()
|
||||
self.placeholder = ComponentView()
|
||||
|
||||
self.vibrancyPlaceholder.view?.removeFromSuperview()
|
||||
self.vibrancyPlaceholder = ComponentView()
|
||||
}
|
||||
|
||||
let attributedPlaceholder = NSMutableAttributedString(string: string, font:Font.regular(17.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.4))
|
||||
if let range = attributedPlaceholder.string.range(of: "#") {
|
||||
attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(component.theme)!, range: NSRange(range, in: attributedPlaceholder.string))
|
||||
@ -897,6 +908,15 @@ public final class MessageInputPanelComponent: Component {
|
||||
containerSize: availableTextFieldSize
|
||||
)
|
||||
} else {
|
||||
let placeholderType = true
|
||||
if let currentPlaceholderType = self.currentPlaceholderType, currentPlaceholderType != placeholderType {
|
||||
self.placeholder.view?.removeFromSuperview()
|
||||
self.placeholder = ComponentView()
|
||||
|
||||
self.vibrancyPlaceholder.view?.removeFromSuperview()
|
||||
self.vibrancyPlaceholder = ComponentView()
|
||||
}
|
||||
|
||||
var placeholderItems: [AnimatedTextComponent.Item] = []
|
||||
switch component.placeholder {
|
||||
case let .plain(string):
|
||||
|
@ -155,6 +155,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/CameraScreen",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/VerifyAlertController",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -17,106 +17,8 @@ import SoftwareVideo
|
||||
import ChatControllerInteraction
|
||||
import PeerInfoVisualMediaPaneNode
|
||||
import PeerInfoPaneNode
|
||||
|
||||
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let file: FileMediaReference
|
||||
private let imageNode: ASImageNode
|
||||
|
||||
private var isPlaying: Bool = false
|
||||
private var isPlayingInternal: Bool = false
|
||||
|
||||
private var frames: [Int: UIImage] = [:]
|
||||
|
||||
private var frameTimes: [Double] = []
|
||||
private var sources: [UniversalSoftwareVideoSource] = []
|
||||
private var disposables: [Int: Disposable] = [:]
|
||||
|
||||
private var currentFrameIndex: Int = 0
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
userLocation: MediaResourceUserLocation,
|
||||
file: FileMediaReference
|
||||
) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.isUserInteractionEnabled = false
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
self.imageNode.clipsToBounds = true
|
||||
|
||||
if let duration = file.media.duration {
|
||||
let frameCount = 5
|
||||
let frameInterval: Double = Double(duration) / Double(frameCount)
|
||||
for i in 0 ..< frameCount {
|
||||
self.frameTimes.append(Double(i) * frameInterval)
|
||||
}
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
for i in 0 ..< self.frameTimes.count {
|
||||
let framePts = self.frameTimes[i]
|
||||
let index = i
|
||||
|
||||
let source = UniversalSoftwareVideoSource(
|
||||
mediaBox: self.context.account.postbox.mediaBox,
|
||||
source: .file(
|
||||
userLocation: userLocation,
|
||||
userContentType: .other,
|
||||
fileReference: self.file
|
||||
),
|
||||
automaticallyFetchHeader: true
|
||||
)
|
||||
self.sources.append(source)
|
||||
self.disposables[index] = (source.takeFrame(at: framePts)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case let .image(image) = result {
|
||||
if let image = image {
|
||||
strongSelf.frames[index] = image
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
for (_, disposable) in self.disposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
self.timer?.invalidate()
|
||||
}
|
||||
|
||||
func updateIsPlaying(_ isPlaying: Bool) {
|
||||
if self.isPlaying == isPlaying {
|
||||
return
|
||||
}
|
||||
self.isPlaying = isPlaying
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
func tick() {
|
||||
let isPlayingInternal = self.isPlaying && self.frames.count == self.frameTimes.count
|
||||
if isPlayingInternal {
|
||||
self.currentFrameIndex = (self.currentFrameIndex + 1) % self.frames.count
|
||||
|
||||
if self.currentFrameIndex < self.frames.count {
|
||||
self.imageNode.image = self.frames[self.currentFrameIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import BatchVideoRendering
|
||||
import GifVideoLayer
|
||||
|
||||
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||
private let mediaBadgeTextColor = UIColor.white
|
||||
@ -142,14 +44,10 @@ private final class VisualMediaItemInteraction {
|
||||
|
||||
private final class VisualMediaItemNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let videoContext: BatchVideoRenderingContext
|
||||
private let interaction: VisualMediaItemInteraction
|
||||
|
||||
private var videoLayerFrameManager: SoftwareVideoLayerFrameManager?
|
||||
private var sampleBufferLayer: SampleBufferLayer?
|
||||
private var displayLink: ConstantDisplayLinkAnimator?
|
||||
private var displayLinkTimestamp: Double = 0.0
|
||||
|
||||
private var frameSequenceThumbnailNode: FrameSequenceThumbnailNode?
|
||||
private var gifVideoLayer: GifVideoLayer?
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let imageNode: TransformImageNode
|
||||
@ -166,9 +64,10 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
private var hasVisibility: Bool = false
|
||||
|
||||
init(context: AccountContext, interaction: VisualMediaItemInteraction) {
|
||||
init(context: AccountContext, interaction: VisualMediaItemInteraction, videoContext: BatchVideoRenderingContext) {
|
||||
self.context = context
|
||||
self.interaction = interaction
|
||||
self.videoContext = videoContext
|
||||
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
@ -295,25 +194,16 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if let file = media as? TelegramMediaFile, file.isAnimated, self.context.sharedContext.energyUsageSettings.autoplayGif {
|
||||
if self.videoLayerFrameManager == nil {
|
||||
let sampleBufferLayer: SampleBufferLayer
|
||||
if let current = self.sampleBufferLayer {
|
||||
sampleBufferLayer = current
|
||||
} else {
|
||||
sampleBufferLayer = takeSampleBufferLayer()
|
||||
self.sampleBufferLayer = sampleBufferLayer
|
||||
self.imageNode.layer.addSublayer(sampleBufferLayer.layer)
|
||||
}
|
||||
|
||||
self.videoLayerFrameManager = SoftwareVideoLayerFrameManager(account: self.context.account, userLocation: .peer(item.message.id.peerId), userContentType: .other, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), layerHolder: sampleBufferLayer)
|
||||
self.videoLayerFrameManager?.start()
|
||||
if self.gifVideoLayer == nil {
|
||||
let gifVideoLayer = GifVideoLayer(context: self.context, batchVideoContext: self.videoContext, userLocation: .peer(item.message.id.peerId), file: FileMediaReference.message(message: MessageReference(item.message), media: file), synchronousLoad: false)
|
||||
self.gifVideoLayer = gifVideoLayer
|
||||
self.imageNode.layer.addSublayer(gifVideoLayer)
|
||||
}
|
||||
} else {
|
||||
if let sampleBufferLayer = self.sampleBufferLayer {
|
||||
sampleBufferLayer.layer.removeFromSuperlayer()
|
||||
self.sampleBufferLayer = nil
|
||||
if let gifVideoLayer = self.gifVideoLayer {
|
||||
gifVideoLayer.removeFromSuperlayer()
|
||||
self.gifVideoLayer = nil
|
||||
}
|
||||
self.videoLayerFrameManager = nil
|
||||
}
|
||||
|
||||
if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) {
|
||||
@ -427,8 +317,8 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
self.containerNode.frame = imageFrame
|
||||
self.imageNode.frame = imageFrame
|
||||
if let sampleBufferLayer = self.sampleBufferLayer {
|
||||
sampleBufferLayer.layer.frame = imageFrame
|
||||
if let gifVideoLayer = self.gifVideoLayer {
|
||||
gifVideoLayer.frame = imageFrame
|
||||
}
|
||||
|
||||
if let mediaDimensions = mediaDimensions {
|
||||
@ -442,54 +332,9 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
func updateIsVisible(_ isVisible: Bool) {
|
||||
self.hasVisibility = isVisible
|
||||
if let _ = self.videoLayerFrameManager {
|
||||
let displayLink: ConstantDisplayLinkAnimator
|
||||
if let current = self.displayLink {
|
||||
displayLink = current
|
||||
} else {
|
||||
displayLink = ConstantDisplayLinkAnimator { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.videoLayerFrameManager?.tick(timestamp: strongSelf.displayLinkTimestamp)
|
||||
strongSelf.displayLinkTimestamp += 1.0 / 30.0
|
||||
}
|
||||
displayLink.frameInterval = 2
|
||||
self.displayLink = displayLink
|
||||
}
|
||||
if let gifVideoLayer = self.gifVideoLayer {
|
||||
gifVideoLayer.shouldBeAnimating = self.hasVisibility && !self.isHidden
|
||||
}
|
||||
self.displayLink?.isPaused = !self.hasVisibility || self.isHidden
|
||||
|
||||
/*if isVisible {
|
||||
if let item = self.item?.0, let file = self.item?.1 as? TelegramMediaFile, !file.isAnimated {
|
||||
if self.frameSequenceThumbnailNode == nil {
|
||||
let frameSequenceThumbnailNode = FrameSequenceThumbnailNode(context: context, file: .message(message: MessageReference(item.message), media: file))
|
||||
self.frameSequenceThumbnailNode = frameSequenceThumbnailNode
|
||||
self.imageNode.addSubnode(frameSequenceThumbnailNode)
|
||||
}
|
||||
if let frameSequenceThumbnailNode = self.frameSequenceThumbnailNode {
|
||||
let size = self.bounds.size
|
||||
frameSequenceThumbnailNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
frameSequenceThumbnailNode.updateLayout(size: size)
|
||||
}
|
||||
} else {
|
||||
if let frameSequenceThumbnailNode = self.frameSequenceThumbnailNode {
|
||||
self.frameSequenceThumbnailNode = nil
|
||||
frameSequenceThumbnailNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let frameSequenceThumbnailNode = self.frameSequenceThumbnailNode {
|
||||
self.frameSequenceThumbnailNode = nil
|
||||
frameSequenceThumbnailNode.removeFromSupernode()
|
||||
}
|
||||
}*/
|
||||
|
||||
self.frameSequenceThumbnailNode?.updateIsPlaying(isVisible)
|
||||
}
|
||||
|
||||
func tick() {
|
||||
self.frameSequenceThumbnailNode?.tick()
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool) {
|
||||
@ -566,7 +411,7 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
} else {
|
||||
self.isHidden = false
|
||||
}
|
||||
self.displayLink?.isPaused = !self.hasVisibility || self.isHidden
|
||||
self.gifVideoLayer?.shouldBeAnimating = self.hasVisibility && !self.isHidden
|
||||
}
|
||||
}
|
||||
|
||||
@ -774,6 +619,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe
|
||||
private let chatControllerInteraction: ChatControllerInteraction
|
||||
private let contentType: ContentType
|
||||
|
||||
private let videoContext: BatchVideoRenderingContext
|
||||
|
||||
weak var parentController: ViewController?
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
@ -807,8 +654,6 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe
|
||||
|
||||
private var decelerationAnimator: ConstantDisplayLinkAnimator?
|
||||
|
||||
private var animationTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private let statusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
self.statusPromise.get()
|
||||
@ -831,6 +676,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe
|
||||
self.floatingHeaderNode = FloatingHeaderNode()
|
||||
self.floatingHeaderNode.alpha = 0.0
|
||||
|
||||
self.videoContext = BatchVideoRenderingContext(context: self.context)
|
||||
|
||||
super.init()
|
||||
|
||||
self._itemInteraction = VisualMediaItemInteraction(
|
||||
@ -876,17 +723,6 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe
|
||||
}
|
||||
})
|
||||
|
||||
let animationTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: true, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
for (_, itemNode) in strongSelf.visibleMediaItems {
|
||||
itemNode.tick()
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
self.animationTimer = animationTimer
|
||||
animationTimer.start()
|
||||
|
||||
self.statusPromise.set(context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: chatLocation.threadId, tag: tagMaskForType(self.contentType))
|
||||
)
|
||||
@ -910,7 +746,6 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe
|
||||
deinit {
|
||||
self.listDisposable.dispose()
|
||||
self.hiddenMediaDisposable?.dispose()
|
||||
self.animationTimer?.invalidate()
|
||||
}
|
||||
|
||||
func ensureMessageIsVisible(id: MessageId) {
|
||||
@ -1164,7 +999,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe
|
||||
if let current = self.visibleMediaItems[stableId] {
|
||||
itemNode = current
|
||||
} else {
|
||||
itemNode = VisualMediaItemNode(context: self.context, interaction: self.itemInteraction)
|
||||
itemNode = VisualMediaItemNode(context: self.context, interaction: self.itemInteraction, videoContext: self.videoContext)
|
||||
self.visibleMediaItems[stableId] = itemNode
|
||||
self.scrollNode.addSubnode(itemNode)
|
||||
}
|
||||
|
@ -675,7 +675,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
case .generic:
|
||||
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod)
|
||||
case .supergroup:
|
||||
createSignal = context.engine.peers.createSupergroup(title: title, description: nil)
|
||||
createSignal = context.engine.peers.createSupergroup(title: title, description: nil, ttlPeriod: ttlPeriod)
|
||||
|> map { peerId -> CreateGroupResult? in
|
||||
return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
|
||||
}
|
||||
@ -730,7 +730,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}
|
||||
|
||||
let createGroupSignal: (Bool) -> Signal<CreateGroupResult?, CreateGroupError> = { isForum in
|
||||
return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum)
|
||||
return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum, ttlPeriod: ttlPeriod)
|
||||
|> map { peerId -> CreateGroupResult? in
|
||||
return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: []))
|
||||
}
|
||||
@ -767,7 +767,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
} else if isForum || group.userAdminRights != nil {
|
||||
createSignal = createGroupSignal(isForum)
|
||||
} else {
|
||||
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: nil)
|
||||
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod)
|
||||
}
|
||||
|
||||
if group.userAdminRights?.rights.contains(.canBeAnonymous) == true {
|
||||
|
Loading…
x
Reference in New Issue
Block a user