mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Story improvements
This commit is contained in:
parent
53f6d62e8b
commit
6910ec9334
@ -795,7 +795,7 @@ public struct StoryCameraTransitionInCoordinator {
|
|||||||
|
|
||||||
public protocol TelegramRootControllerInterface: NavigationController {
|
public protocol TelegramRootControllerInterface: NavigationController {
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
||||||
|
|
||||||
func getContactsController() -> ViewController?
|
func getContactsController() -> ViewController?
|
||||||
func getChatsController() -> ViewController?
|
func getChatsController() -> ViewController?
|
||||||
|
|||||||
@ -1366,9 +1366,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() {
|
||||||
|
storyPeerListView.cancelLoadingItem()
|
||||||
|
}
|
||||||
|
|
||||||
switch subject {
|
switch subject {
|
||||||
case .archive:
|
case .archive:
|
||||||
StoryContainerScreen.openArchivedStories(context: self.context, parentController: self, avatarNode: itemNode.avatarNode)
|
StoryContainerScreen.openArchivedStories(context: self.context, parentController: self, avatarNode: itemNode.avatarNode, sharedProgressDisposable: self.sharedOpenStoryProgressDisposable)
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
StoryContainerScreen.openPeerStories(context: self.context, peerId: peerId, parentController: self, avatarNode: itemNode.avatarNode, sharedProgressDisposable: self.sharedOpenStoryProgressDisposable)
|
StoryContainerScreen.openPeerStories(context: self.context, peerId: peerId, parentController: self, avatarNode: itemNode.avatarNode, sharedProgressDisposable: self.sharedOpenStoryProgressDisposable)
|
||||||
}
|
}
|
||||||
@ -2487,6 +2491,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
self.fullScreenEffectView = nil
|
self.fullScreenEffectView = nil
|
||||||
fullScreenEffectView.removeFromSuperview()
|
fullScreenEffectView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.sharedOpenStoryProgressDisposable.set(nil)
|
||||||
|
|
||||||
|
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() {
|
||||||
|
storyPeerListView.cancelLoadingItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) {
|
func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) {
|
||||||
@ -2692,7 +2702,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||||
let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: { [weak self] target in
|
let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
|
||||||
|
coordinator?.animateIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func storyCameraTransitionOut() -> (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut? {
|
||||||
|
return { [weak self] target, isArchived in
|
||||||
guard let self, let target else {
|
guard let self, let target else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -2711,17 +2727,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
destinationRect: transitionView.bounds,
|
destinationRect: transitionView.bounds,
|
||||||
destinationCornerRadius: transitionView.bounds.height * 0.5
|
destinationCornerRadius: transitionView.bounds.height * 0.5
|
||||||
)
|
)
|
||||||
} else if let rightButtonView = componentView.rightButtonViews["story"] {
|
|
||||||
return StoryCameraTransitionOut(
|
|
||||||
destinationView: rightButtonView,
|
|
||||||
destinationRect: rightButtonView.bounds,
|
|
||||||
destinationCornerRadius: rightButtonView.bounds.height * 0.5
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
|
||||||
coordinator?.animateIn()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3947,6 +3955,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let componentView = self.chatListHeaderView() {
|
if let componentView = self.chatListHeaderView() {
|
||||||
|
self.sharedOpenStoryProgressDisposable.set(nil)
|
||||||
componentView.storyPeerListView()?.setLoadingItem(peerId: peerId, signal: signal)
|
componentView.storyPeerListView()?.setLoadingItem(peerId: peerId, signal: signal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5667,7 +5676,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
if let current = self.storyCameraTransitionInCoordinator {
|
if let current = self.storyCameraTransitionInCoordinator {
|
||||||
coordinator = current
|
coordinator = current
|
||||||
} else {
|
} else {
|
||||||
coordinator = rootController.openStoryCamera(transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target in
|
coordinator = rootController.openStoryCamera(transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target, _ in
|
||||||
guard let self, let target else {
|
guard let self, let target else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -260,6 +260,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private var isLeftAligned: Bool = true
|
private var isLeftAligned: Bool = true
|
||||||
private var itemLayout: ItemLayout?
|
private var itemLayout: ItemLayout?
|
||||||
|
|
||||||
|
public var centerAligned: Bool {
|
||||||
|
return self.validLayout?.4 ?? false
|
||||||
|
}
|
||||||
|
|
||||||
private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)?
|
private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)?
|
||||||
|
|
||||||
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?
|
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?
|
||||||
|
|||||||
@ -4579,7 +4579,7 @@ func replayFinalState(
|
|||||||
mediaAreas: item.mediaAreas,
|
mediaAreas: item.mediaAreas,
|
||||||
text: item.text,
|
text: item.text,
|
||||||
entities: item.entities,
|
entities: item.entities,
|
||||||
views: _internal_updateStoryViewsForMyReaction(views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
|
views: _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
|
||||||
privacy: item.privacy,
|
privacy: item.privacy,
|
||||||
isPinned: item.isPinned,
|
isPinned: item.isPinned,
|
||||||
isExpired: item.isExpired,
|
isExpired: item.isExpired,
|
||||||
@ -4610,7 +4610,7 @@ func replayFinalState(
|
|||||||
mediaAreas: item.mediaAreas,
|
mediaAreas: item.mediaAreas,
|
||||||
text: item.text,
|
text: item.text,
|
||||||
entities: item.entities,
|
entities: item.entities,
|
||||||
views: _internal_updateStoryViewsForMyReaction(views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
|
views: _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
|
||||||
privacy: item.privacy,
|
privacy: item.privacy,
|
||||||
isPinned: item.isPinned,
|
isPinned: item.isPinned,
|
||||||
isExpired: item.isExpired,
|
isExpired: item.isExpired,
|
||||||
|
|||||||
@ -23,10 +23,10 @@ public struct CachedChannelFlags: OptionSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
||||||
public let memberCount: Int32?
|
public var memberCount: Int32?
|
||||||
public let adminCount: Int32?
|
public var adminCount: Int32?
|
||||||
public let bannedCount: Int32?
|
public var bannedCount: Int32?
|
||||||
public let kickedCount: Int32?
|
public var kickedCount: Int32?
|
||||||
|
|
||||||
public init(memberCount: Int32?, adminCount: Int32?, bannedCount: Int32?, kickedCount: Int32?) {
|
public init(memberCount: Int32?, adminCount: Int32?, bannedCount: Int32?, kickedCount: Int32?) {
|
||||||
self.memberCount = memberCount
|
self.memberCount = memberCount
|
||||||
|
|||||||
@ -16,6 +16,6 @@ public final class CachedLocalizationInfos: Codable {
|
|||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
try container.encode(self.list, forKey: "l")
|
try container.encode(self.list, forKey: "t")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,6 +109,7 @@ public struct Namespaces {
|
|||||||
public static let cachedEmojiQueryResults: Int8 = 26
|
public static let cachedEmojiQueryResults: Int8 = 26
|
||||||
public static let cachedPeerStoryListHeads: Int8 = 27
|
public static let cachedPeerStoryListHeads: Int8 = 27
|
||||||
public static let displayedStoryNotifications: Int8 = 28
|
public static let displayedStoryNotifications: Int8 = 28
|
||||||
|
public static let storySendAsPeerIds: Int8 = 29
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct UnorderedItemList {
|
public struct UnorderedItemList {
|
||||||
|
|||||||
@ -1047,7 +1047,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
|||||||
if !peerIds.contains(toPeerId) {
|
if !peerIds.contains(toPeerId) {
|
||||||
peerIds.append(toPeerId)
|
peerIds.append(toPeerId)
|
||||||
}
|
}
|
||||||
transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds)
|
transaction.replaceAllStorySubscriptions(key: subscriptionsKey, state: state, peerIds: peerIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1976,7 +1976,11 @@ public func _internal_setStoryNotificationWasDisplayed(transaction: Transaction,
|
|||||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.displayedStoryNotifications, key: key), entry: CodableEntry(data: Data()))
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.displayedStoryNotifications, key: key), entry: CodableEntry(data: Data()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_updateStoryViewsForMyReaction(views: Stories.Item.Views?, previousReaction: MessageReaction.Reaction?, reaction: MessageReaction.Reaction?) -> Stories.Item.Views? {
|
func _internal_updateStoryViewsForMyReaction(isChannel: Bool, views: Stories.Item.Views?, previousReaction: MessageReaction.Reaction?, reaction: MessageReaction.Reaction?) -> Stories.Item.Views? {
|
||||||
|
if !isChannel {
|
||||||
|
return views
|
||||||
|
}
|
||||||
|
|
||||||
var views = views ?? Stories.Item.Views(seenCount: 0, reactedCount: 0, forwardCount: 0, seenPeerIds: [], reactions: [], hasList: false)
|
var views = views ?? Stories.Item.Views(seenCount: 0, reactedCount: 0, forwardCount: 0, seenPeerIds: [], reactions: [], hasList: false)
|
||||||
|
|
||||||
if let reaction {
|
if let reaction {
|
||||||
@ -2040,7 +2044,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
|||||||
var updatedItemValue: Stories.StoredItem?
|
var updatedItemValue: Stories.StoredItem?
|
||||||
|
|
||||||
let updateViews: (Stories.Item.Views?, MessageReaction.Reaction?) -> Stories.Item.Views? = { views, previousReaction in
|
let updateViews: (Stories.Item.Views?, MessageReaction.Reaction?) -> Stories.Item.Views? = { views, previousReaction in
|
||||||
return _internal_updateStoryViewsForMyReaction(views: views, previousReaction: previousReaction, reaction: reaction)
|
return _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: views, previousReaction: previousReaction, reaction: reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentItems = transaction.getStoryItems(peerId: peerId)
|
var currentItems = transaction.getStoryItems(peerId: peerId)
|
||||||
|
|||||||
@ -839,6 +839,17 @@ public extension TelegramEngine {
|
|||||||
guard let peer = peerView.peer else {
|
guard let peer = peerView.peer else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isPeerHidden = false
|
||||||
|
if let user = peer as? TelegramUser {
|
||||||
|
isPeerHidden = user.storiesHidden ?? false
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
isPeerHidden = channel.storiesHidden ?? false
|
||||||
|
}
|
||||||
|
if isPeerHidden != isHidden {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else {
|
guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -912,6 +923,16 @@ public extension TelegramEngine {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isPeerHidden = false
|
||||||
|
if let user = peer as? TelegramUser {
|
||||||
|
isPeerHidden = user.storiesHidden ?? false
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
isPeerHidden = channel.storiesHidden ?? false
|
||||||
|
}
|
||||||
|
if isPeerHidden != isHidden {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let item = EngineStorySubscriptions.Item(
|
let item = EngineStorySubscriptions.Item(
|
||||||
peer: EnginePeer(peer),
|
peer: EnginePeer(peer),
|
||||||
hasUnseen: false,
|
hasUnseen: false,
|
||||||
|
|||||||
@ -546,9 +546,37 @@ func _internal_adminedPublicChannels(account: Account, scope: AdminedPublicChann
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class CachedStorySendAsPeers: Codable {
|
||||||
|
public let peerIds: [PeerId]
|
||||||
|
|
||||||
|
public init(peerIds: [PeerId]) {
|
||||||
|
self.peerIds = peerIds
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
|
self.peerIds = try container.decode([Int64].self, forKey: "l").map(PeerId.init)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
|
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: "l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> {
|
func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> {
|
||||||
let accountPeerId = account.peerId
|
let accountPeerId = account.peerId
|
||||||
return account.network.request(Api.functions.stories.getChatsToSend())
|
return account.postbox.transaction { transaction -> [Peer]? in
|
||||||
|
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)))?.get(CachedStorySendAsPeers.self) {
|
||||||
|
return entry.peerIds.compactMap(transaction.getPeer)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { cachedPeers in
|
||||||
|
let remote: Signal<[Peer], NoError> = account.network.request(Api.functions.stories.getChatsToSend())
|
||||||
|> retryRequest
|
|> retryRequest
|
||||||
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
||||||
return account.postbox.transaction { transaction -> [Peer] in
|
return account.postbox.transaction { transaction -> [Peer] in
|
||||||
@ -566,11 +594,35 @@ func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> {
|
|||||||
for chat in chats {
|
for chat in chats {
|
||||||
if let peer = transaction.getPeer(chat.peerId) {
|
if let peer = transaction.getPeer(chat.peerId) {
|
||||||
peers.append(peer)
|
peers.append(peer)
|
||||||
|
|
||||||
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat, let participantsCount = participantsCount {
|
||||||
|
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
|
||||||
|
var current = current as? CachedChannelData ?? CachedChannelData()
|
||||||
|
var participantsSummary = current.participantsSummary
|
||||||
|
|
||||||
|
participantsSummary.memberCount = participantsCount
|
||||||
|
|
||||||
|
current = current.withUpdatedParticipantsSummary(participantsSummary)
|
||||||
|
return current
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entry = CodableEntry(CachedStorySendAsPeers(peerIds: peers.map(\.id))) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)), entry: entry)
|
||||||
|
}
|
||||||
|
|
||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let cachedPeers {
|
||||||
|
return .single(cachedPeers) |> then(remote)
|
||||||
|
} else {
|
||||||
|
return remote
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChannelAddressNameAssignmentAvailability {
|
public enum ChannelAddressNameAssignmentAvailability {
|
||||||
|
|||||||
@ -809,6 +809,7 @@ private final class GroupHeaderLayer: UIView {
|
|||||||
func update(
|
func update(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
|
forceNeedsVibrancy: Bool,
|
||||||
layoutType: EmojiPagerContentComponent.ItemLayoutType,
|
layoutType: EmojiPagerContentComponent.ItemLayoutType,
|
||||||
hasTopSeparator: Bool,
|
hasTopSeparator: Bool,
|
||||||
actionButtonTitle: String?,
|
actionButtonTitle: String?,
|
||||||
@ -830,7 +831,7 @@ private final class GroupHeaderLayer: UIView {
|
|||||||
themeUpdated = true
|
themeUpdated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let needsVibrancy = !theme.overallDarkAppearance
|
let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy
|
||||||
|
|
||||||
let textOffsetY: CGFloat
|
let textOffsetY: CGFloat
|
||||||
if hasTopSeparator {
|
if hasTopSeparator {
|
||||||
@ -839,16 +840,22 @@ private final class GroupHeaderLayer: UIView {
|
|||||||
textOffsetY = 0.0
|
textOffsetY = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let subtitleColor: UIColor
|
||||||
|
if theme.overallDarkAppearance && forceNeedsVibrancy {
|
||||||
|
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2)
|
||||||
|
} else {
|
||||||
|
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
let needsTintText: Bool
|
let needsTintText: Bool
|
||||||
if subtitle != nil {
|
if subtitle != nil {
|
||||||
color = theme.chat.inputPanel.primaryTextColor
|
color = theme.chat.inputPanel.primaryTextColor
|
||||||
needsTintText = false
|
needsTintText = false
|
||||||
} else {
|
} else {
|
||||||
color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
color = subtitleColor
|
||||||
needsTintText = true
|
needsTintText = true
|
||||||
}
|
}
|
||||||
let subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
|
||||||
|
|
||||||
let titleHorizontalOffset: CGFloat
|
let titleHorizontalOffset: CGFloat
|
||||||
if isPremiumLocked {
|
if isPremiumLocked {
|
||||||
@ -903,7 +910,7 @@ private final class GroupHeaderLayer: UIView {
|
|||||||
tintClearIconLayer.isHidden = !needsVibrancy
|
tintClearIconLayer.isHidden = !needsVibrancy
|
||||||
|
|
||||||
clearSize = clearIconLayer.bounds.size
|
clearSize = clearIconLayer.bounds.size
|
||||||
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor) {
|
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) {
|
||||||
clearSize = image.size
|
clearSize = image.size
|
||||||
clearIconLayer.contents = image.cgImage
|
clearIconLayer.contents = image.cgImage
|
||||||
}
|
}
|
||||||
@ -1144,7 +1151,7 @@ private final class GroupHeaderLayer: UIView {
|
|||||||
self.separatorLayer = separatorLayer
|
self.separatorLayer = separatorLayer
|
||||||
self.layer.addSublayer(separatorLayer)
|
self.layer.addSublayer(separatorLayer)
|
||||||
}
|
}
|
||||||
separatorLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.cgColor
|
separatorLayer.backgroundColor = subtitleColor.cgColor
|
||||||
separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
|
separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||||
|
|
||||||
let tintSeparatorLayer: SimpleLayer
|
let tintSeparatorLayer: SimpleLayer
|
||||||
@ -1517,6 +1524,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
var context: AccountContext
|
var context: AccountContext
|
||||||
var theme: PresentationTheme
|
var theme: PresentationTheme
|
||||||
|
var forceNeedsVibrancy: Bool
|
||||||
var strings: PresentationStrings
|
var strings: PresentationStrings
|
||||||
var text: String
|
var text: String
|
||||||
var useOpaqueTheme: Bool
|
var useOpaqueTheme: Bool
|
||||||
@ -1535,6 +1543,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1823,10 +1834,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.params = nil
|
self.params = nil
|
||||||
self.update(context: params.context, theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition)
|
self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) {
|
public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) {
|
||||||
let textInputState: EmojiSearchSearchBarComponent.TextInputState
|
let textInputState: EmojiSearchSearchBarComponent.TextInputState
|
||||||
if let textField = self.textField {
|
if let textField = self.textField {
|
||||||
textInputState = .active(hasText: !(textField.text ?? "").isEmpty)
|
textInputState = .active(hasText: !(textField.text ?? "").isEmpty)
|
||||||
@ -1837,6 +1848,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
let params = Params(
|
let params = Params(
|
||||||
context: context,
|
context: context,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
|
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
text: text,
|
text: text,
|
||||||
useOpaqueTheme: useOpaqueTheme,
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
@ -1880,7 +1892,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
|
|
||||||
let sideTextInset: CGFloat = sideInset + 4.0 + 24.0
|
let sideTextInset: CGFloat = sideInset + 4.0 + 24.0
|
||||||
|
|
||||||
if useOpaqueTheme {
|
if theme.overallDarkAppearance && forceNeedsVibrancy {
|
||||||
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor
|
||||||
|
self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor
|
||||||
|
} else if useOpaqueTheme {
|
||||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
|
||||||
self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor
|
self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor
|
||||||
} else {
|
} else {
|
||||||
@ -1891,12 +1906,19 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
self.backgroundLayer.cornerRadius = inputHeight * 0.5
|
self.backgroundLayer.cornerRadius = inputHeight * 0.5
|
||||||
self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5
|
self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5
|
||||||
|
|
||||||
|
let cancelColor: UIColor
|
||||||
|
if theme.overallDarkAppearance && forceNeedsVibrancy {
|
||||||
|
cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
|
||||||
|
} else {
|
||||||
|
cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
let cancelTextSize = self.cancelButtonTitle.update(
|
let cancelTextSize = self.cancelButtonTitle.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(Text(
|
component: AnyComponent(Text(
|
||||||
text: strings.Common_Cancel,
|
text: strings.Common_Cancel,
|
||||||
font: Font.regular(17.0),
|
font: Font.regular(17.0),
|
||||||
color: useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
color: cancelColor
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
||||||
@ -1942,6 +1964,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(EmojiSearchStatusComponent(
|
component: AnyComponent(EmojiSearchStatusComponent(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
|
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
useOpaqueTheme: useOpaqueTheme,
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
content: statusContent
|
content: statusContent
|
||||||
@ -1990,6 +2013,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
component: AnyComponent(EmojiSearchSearchBarComponent(
|
component: AnyComponent(EmojiSearchSearchBarComponent(
|
||||||
context: context,
|
context: context,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
|
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
useOpaqueTheme: useOpaqueTheme,
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
textInputState: textInputState,
|
textInputState: textInputState,
|
||||||
@ -5426,6 +5450,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
let (groupHeaderSize, centralContentWidth) = groupHeaderView.update(
|
let (groupHeaderSize, centralContentWidth) = groupHeaderView.update(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: keyboardChildEnvironment.theme,
|
theme: keyboardChildEnvironment.theme,
|
||||||
|
forceNeedsVibrancy: component.inputInteractionHolder.inputInteraction?.externalBackground != nil,
|
||||||
layoutType: itemLayout.layoutType,
|
layoutType: itemLayout.layoutType,
|
||||||
hasTopSeparator: hasTopSeparator,
|
hasTopSeparator: hasTopSeparator,
|
||||||
actionButtonTitle: actionButtonTitle,
|
actionButtonTitle: actionButtonTitle,
|
||||||
@ -5467,7 +5492,14 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
self.scrollView.layer.insertSublayer(groupBorderLayer, at: 0)
|
self.scrollView.layer.insertSublayer(groupBorderLayer, at: 0)
|
||||||
self.mirrorContentScrollView.layer.addSublayer(groupBorderLayer.tintContainerLayer)
|
self.mirrorContentScrollView.layer.addSublayer(groupBorderLayer.tintContainerLayer)
|
||||||
|
|
||||||
groupBorderLayer.strokeColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.cgColor
|
let borderColor: UIColor
|
||||||
|
if keyboardChildEnvironment.theme.overallDarkAppearance && component.inputInteractionHolder.inputInteraction?.externalBackground != nil {
|
||||||
|
borderColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2)
|
||||||
|
} else {
|
||||||
|
borderColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
|
groupBorderLayer.strokeColor = borderColor.cgColor
|
||||||
groupBorderLayer.tintContainerLayer.strokeColor = UIColor.white.cgColor
|
groupBorderLayer.tintContainerLayer.strokeColor = UIColor.white.cgColor
|
||||||
groupBorderLayer.lineWidth = 1.6
|
groupBorderLayer.lineWidth = 1.6
|
||||||
groupBorderLayer.lineCap = .round
|
groupBorderLayer.lineCap = .round
|
||||||
@ -6854,7 +6886,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||||
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
|
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, forceNeedsVibrancy: component.inputInteractionHolder.inputInteraction?.externalBackground != nil, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
|
||||||
|
|
||||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
|
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
|
||||||
// Temporary workaround for status selection; use a separate search container (see GIF)
|
// Temporary workaround for status selection; use a separate search container (see GIF)
|
||||||
|
|||||||
@ -99,6 +99,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let forceNeedsVibrancy: Bool
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let useOpaqueTheme: Bool
|
let useOpaqueTheme: Bool
|
||||||
let textInputState: TextInputState
|
let textInputState: TextInputState
|
||||||
@ -109,6 +110,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
|
forceNeedsVibrancy: Bool,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
useOpaqueTheme: Bool,
|
useOpaqueTheme: Bool,
|
||||||
textInputState: TextInputState,
|
textInputState: TextInputState,
|
||||||
@ -118,6 +120,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.forceNeedsVibrancy = forceNeedsVibrancy
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.useOpaqueTheme = useOpaqueTheme
|
self.useOpaqueTheme = useOpaqueTheme
|
||||||
self.textInputState = textInputState
|
self.textInputState = textInputState
|
||||||
@ -130,6 +133,9 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -456,7 +462,10 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
if component.useOpaqueTheme {
|
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
|
||||||
|
let tempColor = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
color = tempColor.withMultipliedAlpha(0.3)
|
||||||
|
} else if component.useOpaqueTheme {
|
||||||
color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor
|
color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor
|
||||||
} else {
|
} else {
|
||||||
color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
@ -541,10 +550,18 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
self.visibleItemViews.removeValue(forKey: id)
|
self.visibleItemViews.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selectedColor: UIColor
|
||||||
|
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
|
||||||
|
let tempColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor
|
||||||
|
selectedColor = tempColor.withMultipliedAlpha(0.3)
|
||||||
|
} else {
|
||||||
|
selectedColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor
|
||||||
|
}
|
||||||
|
|
||||||
if let selectedItem = self.selectedItem, let index = items.firstIndex(where: { AnyHashable($0.id) == selectedItem }) {
|
if let selectedItem = self.selectedItem, let index = items.firstIndex(where: { AnyHashable($0.id) == selectedItem }) {
|
||||||
let selectedItemCenter = itemLayout.frame(at: index).center
|
let selectedItemCenter = itemLayout.frame(at: index).center
|
||||||
let selectionSize = CGSize(width: 28.0, height: 28.0)
|
let selectionSize = CGSize(width: 28.0, height: 28.0)
|
||||||
self.selectedItemBackground.backgroundColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor.cgColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor.cgColor
|
self.selectedItemBackground.backgroundColor = selectedColor.cgColor
|
||||||
self.selectedItemTintBackground.backgroundColor = UIColor(white: 1.0, alpha: 0.15).cgColor
|
self.selectedItemTintBackground.backgroundColor = UIColor(white: 1.0, alpha: 0.15).cgColor
|
||||||
self.selectedItemBackground.cornerRadius = selectionSize.height * 0.5
|
self.selectedItemBackground.cornerRadius = selectionSize.height * 0.5
|
||||||
self.selectedItemTintBackground.cornerRadius = selectionSize.height * 0.5
|
self.selectedItemTintBackground.cornerRadius = selectionSize.height * 0.5
|
||||||
@ -609,12 +626,19 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.componentState = state
|
self.componentState = state
|
||||||
|
|
||||||
|
let textColor: UIColor
|
||||||
|
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
|
||||||
|
textColor = component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
|
||||||
|
} else {
|
||||||
|
textColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
let textSize = self.textView.update(
|
let textSize = self.textView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(Text(
|
component: AnyComponent(Text(
|
||||||
text: component.strings.Common_Search,
|
text: component.strings.Common_Search,
|
||||||
font: Font.regular(17.0),
|
font: Font.regular(17.0),
|
||||||
color: component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
color: textColor
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0)
|
containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0)
|
||||||
|
|||||||
@ -70,17 +70,20 @@ final class EmojiSearchStatusComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
|
let forceNeedsVibrancy: Bool
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let useOpaqueTheme: Bool
|
let useOpaqueTheme: Bool
|
||||||
let content: Content
|
let content: Content
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
|
forceNeedsVibrancy: Bool,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
useOpaqueTheme: Bool,
|
useOpaqueTheme: Bool,
|
||||||
content: Content
|
content: Content
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.forceNeedsVibrancy = forceNeedsVibrancy
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.useOpaqueTheme = useOpaqueTheme
|
self.useOpaqueTheme = useOpaqueTheme
|
||||||
self.content = content
|
self.content = content
|
||||||
@ -90,6 +93,9 @@ final class EmojiSearchStatusComponent: Component {
|
|||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -430,7 +436,13 @@ final class EmojiSearchStatusComponent: Component {
|
|||||||
let displaySize = CGSize(width: availableSize.width * UIScreenScale, height: availableSize.height * UIScreenScale)
|
let displaySize = CGSize(width: availableSize.width * UIScreenScale, height: availableSize.height * UIScreenScale)
|
||||||
self.displaySize = displaySize
|
self.displaySize = displaySize
|
||||||
|
|
||||||
let overlayColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
let overlayColor: UIColor
|
||||||
|
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
|
||||||
|
overlayColor = component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
|
||||||
|
} else {
|
||||||
|
overlayColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
let baseColor: UIColor = .white
|
let baseColor: UIColor = .white
|
||||||
|
|
||||||
if self.contentView.tintColor != overlayColor {
|
if self.contentView.tintColor != overlayColor {
|
||||||
|
|||||||
@ -1090,7 +1090,7 @@ public final class GifPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||||
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
|
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, forceNeedsVibrancy: false, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
|
||||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||||
let _ = self
|
let _ = self
|
||||||
let _ = completed
|
let _ = completed
|
||||||
|
|||||||
@ -291,7 +291,7 @@ public final class LottieComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.scheduledPlayOnce {
|
if self.scheduledPlayOnce && self.isEffectivelyVisible {
|
||||||
self.scheduledPlayOnce = false
|
self.scheduledPlayOnce = false
|
||||||
self.playOnce()
|
self.playOnce()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -718,7 +718,6 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
|
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2694,12 +2693,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
self.stateSubject.set(.single(state))
|
self.stateSubject.set(.single(state))
|
||||||
|
|
||||||
for peer in peers {
|
|
||||||
if case let .channel(channel) = peer, participants[channel.id] == nil {
|
|
||||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
case .stories:
|
case .stories:
|
||||||
@ -2811,9 +2804,11 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
for channel in adminedChannels {
|
for channel in adminedChannels {
|
||||||
if case let .channel(channel) = channel, channel.hasPermission(.postStories) {
|
if case let .channel(channel) = channel, channel.hasPermission(.postStories) {
|
||||||
|
if !sendAsPeers.contains(where: { $0.id == channel.id }) {
|
||||||
sendAsPeers.append(contentsOf: adminedChannels)
|
sendAsPeers.append(contentsOf: adminedChannels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (peersMap, everyonePeers, contactsPeers, selectedPeers) = savedPeers
|
let (peersMap, everyonePeers, contactsPeers, selectedPeers) = savedPeers
|
||||||
var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] = [:]
|
var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] = [:]
|
||||||
@ -2833,12 +2828,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
self.stateSubject.set(.single(state))
|
self.stateSubject.set(.single(state))
|
||||||
|
|
||||||
for peer in adminedChannels {
|
|
||||||
if case let .channel(channel) = peer, participants[channel.id] == nil {
|
|
||||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
case let .chats(isGrayList):
|
case let .chats(isGrayList):
|
||||||
@ -2958,12 +2947,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
self.stateSubject.set(.single(state))
|
self.stateSubject.set(.single(state))
|
||||||
|
|
||||||
for peer in state.peers {
|
|
||||||
if case let .channel(channel) = peer, participants[channel.id] == nil {
|
|
||||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
case let .contacts(base):
|
case let .contacts(base):
|
||||||
@ -3107,12 +3090,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
self.stateSubject.set(.single(state))
|
self.stateSubject.set(.single(state))
|
||||||
|
|
||||||
for peer in state.peers {
|
|
||||||
if case let .channel(channel) = peer, participants[channel.id] == nil {
|
|
||||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import Postbox
|
|||||||
import AvatarNode
|
import AvatarNode
|
||||||
|
|
||||||
public extension StoryContainerScreen {
|
public extension StoryContainerScreen {
|
||||||
static func openArchivedStories(context: AccountContext, parentController: ViewController, avatarNode: AvatarNode) {
|
static func openArchivedStories(context: AccountContext, parentController: ViewController, avatarNode: AvatarNode, sharedProgressDisposable: MetaDisposable?) {
|
||||||
let storyContent = StoryContentContextImpl(context: context, isHidden: true, focusedPeerId: nil, singlePeer: false)
|
let storyContent = StoryContentContextImpl(context: context, isHidden: true, focusedPeerId: nil, singlePeer: false)
|
||||||
let signal = storyContent.state
|
let signal = storyContent.state
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -82,7 +82,10 @@ public extension StoryContainerScreen {
|
|||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
|
|
||||||
let _ = avatarNode.pushLoadingStatus(signal: signal)
|
let disposable = avatarNode.pushLoadingStatus(signal: signal)
|
||||||
|
if let sharedProgressDisposable {
|
||||||
|
sharedProgressDisposable.set(disposable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func openPeerStories(context: AccountContext, peerId: EnginePeer.Id, parentController: ViewController, avatarNode: AvatarNode?, sharedProgressDisposable: MetaDisposable? = nil) {
|
static func openPeerStories(context: AccountContext, peerId: EnginePeer.Id, parentController: ViewController, avatarNode: AvatarNode?, sharedProgressDisposable: MetaDisposable? = nil) {
|
||||||
|
|||||||
@ -91,7 +91,7 @@ private final class MuteMonitor {
|
|||||||
|
|
||||||
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
||||||
var shouldBegin: ((UITouch) -> Bool)?
|
var shouldBegin: ((UITouch) -> Bool)?
|
||||||
var updateIsTracking: ((Bool) -> Void)?
|
var updateIsTracking: ((CGPoint?) -> Void)?
|
||||||
|
|
||||||
override var state: UIGestureRecognizer.State {
|
override var state: UIGestureRecognizer.State {
|
||||||
didSet {
|
didSet {
|
||||||
@ -116,7 +116,7 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
|||||||
self.isValidated = false
|
self.isValidated = false
|
||||||
if self.isTracking {
|
if self.isTracking {
|
||||||
self.isTracking = false
|
self.isTracking = false
|
||||||
self.updateIsTracking?(false)
|
self.updateIsTracking?(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
|||||||
|
|
||||||
if !self.isTracking {
|
if !self.isTracking {
|
||||||
self.isTracking = true
|
self.isTracking = true
|
||||||
self.updateIsTracking?(true)
|
self.updateIsTracking?(touches.first?.location(in: self.view))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,13 +450,39 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
|
|
||||||
let longPressRecognizer = StoryLongPressRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
|
let longPressRecognizer = StoryLongPressRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
|
||||||
longPressRecognizer.delegate = self
|
longPressRecognizer.delegate = self
|
||||||
longPressRecognizer.updateIsTracking = { [weak self] isTracking in
|
longPressRecognizer.updateIsTracking = { [weak self] point in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.isHoldingTouch = isTracking
|
guard let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var point = point
|
||||||
|
if let pointValue = point {
|
||||||
|
if !itemSetComponentView.allowsInstantPauseOnTouch(point: self.convert(pointValue, to: itemSetComponentView)) {
|
||||||
|
point = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if point != nil {
|
||||||
|
if !self.isHoldingTouch {
|
||||||
|
self.isHoldingTouch = true
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.isHoldingTouch {
|
||||||
|
self.isHoldingTouch = false
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
longPressRecognizer.shouldBegin = { [weak self] touch in
|
longPressRecognizer.shouldBegin = { [weak self] touch in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -162,6 +162,13 @@ final class StoryItemContentComponent: Component {
|
|||||||
self.currentFetchPriority?.disposable.dispose()
|
self.currentFetchPriority?.disposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allowsInstantPauseOnTouch(point: CGPoint) -> Bool {
|
||||||
|
if let _ = self.overlaysView.hitTest(self.convert(self.convert(point, to: self.overlaysView), to: self.overlaysView), with: nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private func performActionAfterImageContentLoaded(update: Bool) {
|
private func performActionAfterImageContentLoaded(update: Bool) {
|
||||||
self.initializeVideoIfReady(update: update)
|
self.initializeVideoIfReady(update: update)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -449,6 +449,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
var reactionContextNode: ReactionContextNode?
|
var reactionContextNode: ReactionContextNode?
|
||||||
weak var disappearingReactionContextNode: ReactionContextNode?
|
weak var disappearingReactionContextNode: ReactionContextNode?
|
||||||
|
weak var willDismissReactionContextNode: ReactionContextNode?
|
||||||
var displayLikeReactions: Bool = false
|
var displayLikeReactions: Bool = false
|
||||||
var tempReactionsGesture: ContextGesture?
|
var tempReactionsGesture: ContextGesture?
|
||||||
var waitingForReactionAnimateOutToLike: MessageReaction.Reaction?
|
var waitingForReactionAnimateOutToLike: MessageReaction.Reaction?
|
||||||
@ -711,7 +712,32 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allowsInstantPauseOnTouch(point: CGPoint) -> Bool {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let visibleItem = self.visibleItems[component.slice.item.storyItem.id] else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let itemView = visibleItem.view.view as? StoryItemContentComponent.View else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let localPoint = self.convert(point, to: itemView)
|
||||||
|
if itemView.bounds.contains(localPoint) {
|
||||||
|
if !itemView.allowsInstantPauseOnTouch(point: localPoint) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func isPointInsideContentArea(point: CGPoint) -> Bool {
|
func isPointInsideContentArea(point: CGPoint) -> Bool {
|
||||||
|
if self.reactionContextNode != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if let inputPanelView = self.inputPanel.view, inputPanelView.alpha != 0.0 {
|
if let inputPanelView = self.inputPanel.view, inputPanelView.alpha != 0.0 {
|
||||||
if inputPanelView.frame.contains(point) {
|
if inputPanelView.frame.contains(point) {
|
||||||
return false
|
return false
|
||||||
@ -874,6 +900,13 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
if self.displayLikeReactions {
|
if self.displayLikeReactions {
|
||||||
self.displayLikeReactions = false
|
self.displayLikeReactions = false
|
||||||
|
self.sendMessageContext.currentInputMode = .text
|
||||||
|
self.willDismissReactionContextNode = self.reactionContextNode
|
||||||
|
|
||||||
|
if hasFirstResponder(self) {
|
||||||
|
self.endEditing(true)
|
||||||
|
}
|
||||||
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||||
self.updateIsProgressPaused()
|
self.updateIsProgressPaused()
|
||||||
} else if self.hasActiveDeactivateableInput() {
|
} else if self.hasActiveDeactivateableInput() {
|
||||||
@ -1240,6 +1273,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
return self.itemsContainerView
|
return self.itemsContainerView
|
||||||
|
} else if self.viewListDisplayState == .half && result.isDescendant(of: self.itemsContainerView) {
|
||||||
|
return self.itemsContainerView
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -4077,19 +4112,18 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reactionsAnchorRect: CGRect
|
let reactionsAnchorRect: CGRect
|
||||||
if self.displayLikeReactions, let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View, let likeButtonView = inputPanelView.likeButtonView {
|
|
||||||
|
if self.inputPanelExternalState.isEditing, let inputPanelFrameValue {
|
||||||
|
reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrameValue.maxX - 40.0, y: inputPanelFrameValue.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
|
||||||
|
} else if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View, let likeButtonView = inputPanelView.likeButtonView {
|
||||||
var likeRect = likeButtonView.convert(likeButtonView.bounds, to: self)
|
var likeRect = likeButtonView.convert(likeButtonView.bounds, to: self)
|
||||||
likeRect.origin.y -= 15.0
|
likeRect.origin.y -= 15.0
|
||||||
likeRect.size.height += 15.0
|
likeRect.size.height += 15.0
|
||||||
likeRect.origin.x -= 30.0
|
likeRect.origin.x -= 30.0
|
||||||
reactionsAnchorRect = likeRect
|
reactionsAnchorRect = likeRect
|
||||||
} else {
|
|
||||||
if let inputPanelFrameValue {
|
|
||||||
reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrameValue.maxX - 40.0, y: inputPanelFrameValue.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
|
|
||||||
} else {
|
} else {
|
||||||
reactionsAnchorRect = CGRect()
|
reactionsAnchorRect = CGRect()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var effectiveDisplayReactions = false
|
var effectiveDisplayReactions = false
|
||||||
if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText {
|
if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText {
|
||||||
@ -4114,7 +4148,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
effectiveDisplayReactions = false
|
effectiveDisplayReactions = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if let reactionContextNode = self.reactionContextNode, (reactionContextNode.isReactionSearchActive && !reactionContextNode.isAnimatingOutToReaction && !reactionContextNode.isAnimatingOut) {
|
if let reactionContextNode = self.reactionContextNode, self.willDismissReactionContextNode !== reactionContextNode, (reactionContextNode.isReactionSearchActive && !reactionContextNode.isAnimatingOutToReaction && !reactionContextNode.isAnimatingOut) {
|
||||||
effectiveDisplayReactions = true
|
effectiveDisplayReactions = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4203,11 +4237,11 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in
|
reactionContextNode.reactionSelected = { [weak self, weak reactionContextNode] updateReaction, _ in
|
||||||
guard let self else {
|
guard let self, let reactionContextNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let action: () -> Void = { [weak self] in
|
let action: () -> Void = { [weak self, weak reactionContextNode] in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -4265,7 +4299,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}, completion: { [weak targetView, weak reactionContextNode] in
|
}, completion: { [weak targetView, weak reactionContextNode] in
|
||||||
targetView?.removeFromSuperview()
|
targetView?.removeFromSuperview()
|
||||||
if let reactionContextNode {
|
if let reactionContextNode {
|
||||||
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
|
||||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
||||||
reactionContextNode?.view.removeFromSuperview()
|
reactionContextNode?.view.removeFromSuperview()
|
||||||
})
|
})
|
||||||
@ -4486,7 +4519,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if let reactionContextNode = self.disappearingReactionContextNode {
|
if let reactionContextNode = self.disappearingReactionContextNode {
|
||||||
if !reactionContextNode.isAnimatingOutToReaction {
|
if !reactionContextNode.isAnimatingOutToReaction {
|
||||||
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
|
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: reactionContextNode.centerAligned, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5852,6 +5885,51 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isHidden = false
|
||||||
|
if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden {
|
||||||
|
isHidden = storiesHidden
|
||||||
|
}
|
||||||
|
items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden)
|
||||||
|
|
||||||
|
let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string
|
||||||
|
let tooltipScreen = TooltipScreen(
|
||||||
|
context: component.context,
|
||||||
|
account: component.context.account,
|
||||||
|
sharedContext: component.context.sharedContext,
|
||||||
|
text: .markdown(text: text),
|
||||||
|
style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0),
|
||||||
|
icon: .peer(peer: component.slice.peer, isStory: true),
|
||||||
|
action: TooltipScreen.Action(
|
||||||
|
title: component.strings.Undo_Undo,
|
||||||
|
action: {
|
||||||
|
component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
location: .bottom,
|
||||||
|
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
|
||||||
|
)
|
||||||
|
tooltipScreen.willBecomeDismissed = { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.sendMessageContext.tooltipScreen = nil
|
||||||
|
self.updateIsProgressPaused()
|
||||||
|
}
|
||||||
|
self.sendMessageContext.tooltipScreen?.dismiss()
|
||||||
|
self.sendMessageContext.tooltipScreen = tooltipScreen
|
||||||
|
self.updateIsProgressPaused()
|
||||||
|
component.controller()?.present(tooltipScreen, in: .current)
|
||||||
|
})))
|
||||||
|
|
||||||
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) {
|
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) {
|
||||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
@ -6004,6 +6082,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
), nil)
|
), nil)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
var isHidden = false
|
var isHidden = false
|
||||||
if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden {
|
if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden {
|
||||||
@ -6012,6 +6091,18 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
isHidden = storiesHidden
|
isHidden = storiesHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var canArchive = false
|
||||||
|
if isHidden {
|
||||||
|
canArchive = true
|
||||||
|
} else {
|
||||||
|
if case .user = component.slice.peer, !component.slice.peer.isService {
|
||||||
|
canArchive = true
|
||||||
|
} else if case .channel = component.slice.peer {
|
||||||
|
canArchive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if canArchive {
|
||||||
items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
|
|||||||
@ -472,10 +472,21 @@ public final class StoryPeerListComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func cancelLoadingItem() {
|
||||||
|
self.loadingItemDisposable?.dispose()
|
||||||
|
self.loadingItemDisposable = nil
|
||||||
|
|
||||||
|
if self.loadingItemId != nil {
|
||||||
|
self.loadingItemId = nil
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func setLoadingItem(peerId: EnginePeer.Id, signal: Signal<Never, NoError>) {
|
public func setLoadingItem(peerId: EnginePeer.Id, signal: Signal<Never, NoError>) {
|
||||||
var applyLoadingItem = true
|
var applyLoadingItem = true
|
||||||
|
|
||||||
self.loadingItemDisposable?.dispose()
|
self.loadingItemDisposable?.dispose()
|
||||||
self.loadingItemDisposable = (signal |> deliverOnMainQueue).start(completed: { [weak self] in
|
let loadingItemDisposable = (signal |> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -483,6 +494,7 @@ public final class StoryPeerListComponent: Component {
|
|||||||
applyLoadingItem = false
|
applyLoadingItem = false
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
})
|
})
|
||||||
|
self.loadingItemDisposable = loadingItemDisposable
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1285,9 +1297,8 @@ public final class StoryPeerListComponent: Component {
|
|||||||
titleContentOffset = collapsedTitleOffset
|
titleContentOffset = collapsedTitleOffset
|
||||||
} else {
|
} else {
|
||||||
titleContentOffset = titleMinContentOffset.interpolate(to: ((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: min(1.0, collapsedState.maxFraction) * (1.0 - collapsedState.activityFraction))
|
titleContentOffset = titleMinContentOffset.interpolate(to: ((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: min(1.0, collapsedState.maxFraction) * (1.0 - collapsedState.activityFraction))
|
||||||
}
|
|
||||||
|
|
||||||
titleContentOffset += -expandBoundsFraction * 4.0
|
titleContentOffset += -expandBoundsFraction * 4.0
|
||||||
|
}
|
||||||
|
|
||||||
var titleIndicatorSize: CGSize?
|
var titleIndicatorSize: CGSize?
|
||||||
if collapsedState.activityFraction != 0.0 {
|
if collapsedState.activityFraction != 0.0 {
|
||||||
|
|||||||
@ -266,7 +266,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
|
public func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
|
||||||
guard let controller = self.viewControllers.last as? ViewController else {
|
guard let controller = self.viewControllers.last as? ViewController else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -275,6 +275,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
let context = self.context
|
let context = self.context
|
||||||
|
|
||||||
var storyTarget: Stories.PendingTarget?
|
var storyTarget: Stories.PendingTarget?
|
||||||
|
var isPeerArchived = false
|
||||||
|
var updatedTransitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?
|
||||||
|
|
||||||
var presentImpl: ((ViewController) -> Void)?
|
var presentImpl: ((ViewController) -> Void)?
|
||||||
var returnToCameraImpl: (() -> Void)?
|
var returnToCameraImpl: (() -> Void)?
|
||||||
@ -295,7 +297,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
transitionOut: { finished in
|
transitionOut: { finished in
|
||||||
if let transitionOut = transitionOut(finished ? storyTarget : nil), let destinationView = transitionOut.destinationView {
|
if let transitionOut = (updatedTransitionOut ?? transitionOut)(finished ? storyTarget : nil, isPeerArchived), let destinationView = transitionOut.destinationView {
|
||||||
return CameraScreen.TransitionOut(
|
return CameraScreen.TransitionOut(
|
||||||
destinationView: destinationView,
|
destinationView: destinationView,
|
||||||
destinationRect: transitionOut.destinationRect,
|
destinationRect: transitionOut.destinationRect,
|
||||||
@ -353,7 +355,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
isEditing: false,
|
isEditing: false,
|
||||||
transitionIn: transitionIn,
|
transitionIn: transitionIn,
|
||||||
transitionOut: { finished, isNew in
|
transitionOut: { finished, isNew in
|
||||||
if finished, let transitionOut = transitionOut(storyTarget), let destinationView = transitionOut.destinationView {
|
if finished, let transitionOut = (updatedTransitionOut ?? transitionOut)(storyTarget, false), let destinationView = transitionOut.destinationView {
|
||||||
return MediaEditorScreen.TransitionOut(
|
return MediaEditorScreen.TransitionOut(
|
||||||
destinationView: destinationView,
|
destinationView: destinationView,
|
||||||
destinationRect: transitionOut.destinationRect,
|
destinationRect: transitionOut.destinationRect,
|
||||||
@ -375,13 +377,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = self.context
|
|
||||||
if let rootTabController = self.rootTabController {
|
|
||||||
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
|
|
||||||
rootTabController.selectedIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let target: Stories.PendingTarget
|
let target: Stories.PendingTarget
|
||||||
let targetPeerId: EnginePeer.Id
|
let targetPeerId: EnginePeer.Id
|
||||||
if let sendAsPeerId = options.sendAsPeerId {
|
if let sendAsPeerId = options.sendAsPeerId {
|
||||||
@ -393,16 +388,49 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
}
|
}
|
||||||
storyTarget = target
|
storyTarget = target
|
||||||
|
|
||||||
|
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
|
guard let self, let peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .user(user) = peer {
|
||||||
|
isPeerArchived = user.storiesHidden ?? false
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
isPeerArchived = channel.storiesHidden ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = self.context
|
||||||
|
if let rootTabController = self.rootTabController {
|
||||||
|
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
|
||||||
|
rootTabController.selectedIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let completionImpl: () -> Void = { [weak self] in
|
let completionImpl: () -> Void = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let chatListController = self.chatListController as? ChatListControllerImpl {
|
var chatListController: ChatListControllerImpl?
|
||||||
|
|
||||||
|
if isPeerArchived {
|
||||||
|
var viewControllers = self.viewControllers
|
||||||
|
|
||||||
|
let archiveController = ChatListControllerImpl(context: context, location: .chatList(groupId: .archive), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
|
||||||
|
updatedTransitionOut = archiveController.storyCameraTransitionOut()
|
||||||
|
chatListController = archiveController
|
||||||
|
viewControllers.insert(archiveController, at: 1)
|
||||||
|
self.setViewControllers(viewControllers, animated: false)
|
||||||
|
} else {
|
||||||
|
chatListController = self.chatListController as? ChatListControllerImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
if let chatListController {
|
||||||
let _ = (chatListController.hasPendingStories
|
let _ = (chatListController.hasPendingStories
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> timeout(0.25, queue: .mainQueue(), alternate: .single(true))
|
|> timeout(isPeerArchived ? 0.5 : 0.25, queue: .mainQueue(), alternate: .single(true))
|
||||||
|> deliverOnMainQueue).start(completed: { [weak chatListController] in
|
|> deliverOnMainQueue).start(completed: { [weak chatListController] in
|
||||||
guard let chatListController else {
|
guard let chatListController else {
|
||||||
return
|
return
|
||||||
@ -471,6 +499,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
dismissCameraImpl?()
|
dismissCameraImpl?()
|
||||||
|
})
|
||||||
} as (Int64, MediaEditorScreen.Result?, [MediaArea], NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void
|
} as (Int64, MediaEditorScreen.Result?, [MediaArea], NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
controller.cancelled = { showDraftTooltip in
|
controller.cancelled = { showDraftTooltip in
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user