Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
overtake 2020-06-08 16:12:47 +03:00
commit db713754e1
152 changed files with 6488 additions and 5024 deletions

2
.gitmodules vendored
View File

@ -22,4 +22,4 @@ url=https://github.com/bazelbuild/rules_apple.git
url = https://chromium.googlesource.com/chromium/tools/depot_tools.git url = https://chromium.googlesource.com/chromium/tools/depot_tools.git
[submodule "third-party/webrtc/webrtc-ios"] [submodule "third-party/webrtc/webrtc-ios"]
path = third-party/webrtc/webrtc-ios path = third-party/webrtc/webrtc-ios
url=../webrtc-ios.git url=https://github.com/ali-fareed/webrtc-ios.git

View File

@ -3,7 +3,7 @@
include Utils.makefile include Utils.makefile
APP_VERSION="6.2" APP_VERSION="6.2.1"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)

View File

@ -3,7 +3,7 @@
@implementation Serialization @implementation Serialization
- (NSUInteger)currentLayer { - (NSUInteger)currentLayer {
return 114; return 115;
} }
- (id _Nullable)parseMessage:(NSData * _Nullable)data { - (id _Nullable)parseMessage:(NSData * _Nullable)data {

View File

@ -5522,5 +5522,19 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.Timer.Title" = "Send With Timer"; "Conversation.Timer.Title" = "Send With Timer";
"Conversation.Timer.Send" = "Send With Timer"; "Conversation.Timer.Send" = "Send With Timer";
"Paint.Pen" = "Pen";
"Paint.Marker" = "Marker";
"Paint.Neon" = "Neon";
"Paint.Arrow" = "Arrow";
"Conversation.NoticeInvitedByInChannel" = "%@ invited you to this channel"; "Conversation.NoticeInvitedByInChannel" = "%@ invited you to this channel";
"Conversation.NoticeInvitedByInGroup" = "%@ invited you to this group"; "Conversation.NoticeInvitedByInGroup" = "%@ invited you to this group";
"ChatList.MessagePhotos_1" = "1 Photo";
"ChatList.MessagePhotos_any" = "%@ Photos";
"ChatList.MessageVideos_1" = "1 Videos";
"ChatList.MessageVideos_any" = "%@ Videos";
"Conversation.PrivateChannelTimeLimitedAlertTitle" = "Join Channel";
"Conversation.PrivateChannelTimeLimitedAlertText" = "This channel is private. Please join it to continue viewing its content.";
"Conversation.PrivateChannelTimeLimitedAlertJoin" = "Join";

View File

@ -192,6 +192,16 @@ public enum NavigateToChatKeepStack {
case never case never
} }
public final class ChatPeekTimeout {
public let deadline: Int32
public let linkData: String
public init(deadline: Int32, linkData: String) {
self.deadline = deadline
self.linkData = linkData
}
}
public final class NavigateToChatControllerParams { public final class NavigateToChatControllerParams {
public let navigationController: NavigationController public let navigationController: NavigationController
public let chatController: ChatController? public let chatController: ChatController?
@ -206,12 +216,13 @@ public final class NavigateToChatControllerParams {
public let purposefulAction: (() -> Void)? public let purposefulAction: (() -> Void)?
public let scrollToEndIfExists: Bool public let scrollToEndIfExists: Bool
public let activateMessageSearch: Bool public let activateMessageSearch: Bool
public let peekData: ChatPeekTimeout?
public let animated: Bool public let animated: Bool
public let options: NavigationAnimationOptions public let options: NavigationAnimationOptions
public let parentGroupId: PeerGroupId? public let parentGroupId: PeerGroupId?
public let completion: (ChatController) -> Void public let completion: (ChatController) -> Void
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: Bool = false, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) { public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: Bool = false, peekData: ChatPeekTimeout? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
self.navigationController = navigationController self.navigationController = navigationController
self.chatController = chatController self.chatController = chatController
self.context = context self.context = context
@ -225,6 +236,7 @@ public final class NavigateToChatControllerParams {
self.purposefulAction = purposefulAction self.purposefulAction = purposefulAction
self.scrollToEndIfExists = scrollToEndIfExists self.scrollToEndIfExists = scrollToEndIfExists
self.activateMessageSearch = activateMessageSearch self.activateMessageSearch = activateMessageSearch
self.peekData = peekData
self.animated = animated self.animated = animated
self.options = options self.options = options
self.parentGroupId = parentGroupId self.parentGroupId = parentGroupId

View File

@ -27,7 +27,7 @@ public struct ChatControllerInitialBotStart {
public enum ChatControllerInteractionNavigateToPeer { public enum ChatControllerInteractionNavigateToPeer {
case `default` case `default`
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?) case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?)
case info case info
case withBotStartPayload(ChatControllerInitialBotStart) case withBotStartPayload(ChatControllerInitialBotStart)
} }

View File

@ -13,8 +13,8 @@ public final class AlertControllerContext {
} }
} }
public func textAlertController(alertContext: AlertControllerContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true) -> AlertController { public func textAlertController(alertContext: AlertControllerContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController {
let controller = standardTextAlertController(theme: alertContext.theme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset) let controller = standardTextAlertController(theme: alertContext.theme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap)
let presentationDataDisposable = alertContext.themeSignal.start(next: { [weak controller] theme in let presentationDataDisposable = alertContext.themeSignal.start(next: { [weak controller] theme in
controller?.theme = theme controller?.theme = theme
}) })
@ -36,7 +36,7 @@ public func richTextAlertController(alertContext: AlertControllerContext, title:
} }
action.action() action.action()
}) })
}, actionLayout: actionLayout), allowInputInset: allowInputInset) }, actionLayout: actionLayout, dismissOnOutsideTap: true), allowInputInset: allowInputInset)
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
} }

View File

@ -330,7 +330,7 @@ public final class AvatarNode: ASDisplayNode {
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) { if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
self.contents = nil self.contents = nil
self.displaySuspended = true self.displaySuspended = true
self.imageReady.set(self.imageNode.ready) self.imageReady.set(self.imageNode.contentReady)
self.imageNode.setSignal(signal |> beforeNext { [weak self] next in self.imageNode.setSignal(signal |> beforeNext { [weak self] next in
Queue.mainQueue().async { Queue.mainQueue().async {
self?.unroundedImage = next?.1 self?.unroundedImage = next?.1

View File

@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
}, present: { _ in }) }, present: { _ in })
let items = (0 ..< 2).map { _ -> ChatListItem in let items = (0 ..< 2).map { _ -> ChatListItem in
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
} }
var itemNodes: [ChatListItemNode] = [] var itemNodes: [ChatListItemNode] = []

View File

@ -485,7 +485,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} }
}) })
case let .message(message, peer, readState, presentationData): case let .message(message, peer, readState, presentationData):
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
case let .addContact(phoneNumber, theme, strings): case let .addContact(phoneNumber, theme, strings):
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
interaction.addContact(phoneNumber) interaction.addContact(phoneNumber)
@ -1385,8 +1385,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
bounds = selectedItemNode.bounds bounds = selectedItemNode.bounds
} }
switch item.content { switch item.content {
case let .peer(message, peer, _, _, _, _, _, _, _, _, _, _): case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _):
return (selectedItemNode.view, bounds, message?.id ?? peer.peerId) return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId)
case let .groupReference(groupId, _, _, _, _): case let .groupReference(groupId, _, _, _, _):
return (selectedItemNode.view, bounds, groupId) return (selectedItemNode.view, bounds, groupId)
} }

View File

@ -20,7 +20,7 @@ import ChatListSearchItemNode
import ContextUI import ContextUI
public enum ChatListItemContent { public enum ChatListItemContent {
case peer(message: Message?, peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool) case peer(messages: [Message], peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool)
case groupReference(groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, unreadState: PeerGroupUnreadCountersCombinedSummary, hiddenByDefault: Bool) case groupReference(groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, unreadState: PeerGroupUnreadCountersCombinedSummary, hiddenByDefault: Bool)
public var chatLocation: ChatLocation? { public var chatLocation: ChatLocation? {
@ -124,8 +124,8 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
public func selected(listView: ListView) { public func selected(listView: ListView) {
switch self.content { switch self.content {
case let .peer(message, peer, _, _, _, _, _, _, promoInfo, _, _, _): case let .peer(messages, peer, _, _, _, _, _, _, promoInfo, _, _, _):
if let message = message, let peer = peer.peer { if let message = messages.last, let peer = peer.peer {
self.interaction.messageSelected(peer, message, promoInfo) self.interaction.messageSelected(peer, message, promoInfo)
} else if let peer = peer.peer { } else if let peer = peer.peer {
self.interaction.peerSelected(peer, promoInfo) self.interaction.peerSelected(peer, promoInfo)
@ -328,6 +328,61 @@ private final class CachedChatListSearchResult {
} }
} }
private final class ChatListMediaPreviewNode: ASDisplayNode {
private let context: AccountContext
private let message: Message
private let media: Media
private let imageNode: TransformImageNode
private var requestedImage: Bool = false
private var disposable: Disposable?
init(context: AccountContext, message: Message, media: Media) {
self.context = context
self.message = message
self.media = media
self.imageNode = TransformImageNode()
super.init()
self.addSubnode(self.imageNode)
}
deinit {
self.disposable?.dispose()
}
func updateLayout(size: CGSize, synchronousLoads: Bool) {
var dimensions = CGSize(width: 100.0, height: 100.0)
if let image = self.media as? TelegramMediaImage {
if let largest = largestImageRepresentation(image.representations) {
dimensions = largest.dimensions.cgSize
if !self.requestedImage {
self.requestedImage = true
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads)
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
}
}
} else if let file = self.media as? TelegramMediaFile {
if let mediaDimensions = file.dimensions {
dimensions = mediaDimensions.cgSize
if !self.requestedImage {
self.requestedImage = true
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true)
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
}
}
}
let makeLayout = self.imageNode.asyncLayout()
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
}
}
class ChatListItemNode: ItemListRevealOptionsItemNode { class ChatListItemNode: ItemListRevealOptionsItemNode {
var item: ChatListItem? var item: ChatListItem?
@ -342,7 +397,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let measureNode: TextNode let measureNode: TextNode
private var currentItemHeight: CGFloat? private var currentItemHeight: CGFloat?
let textNode: TextNode let textNode: TextNode
let contentImageNode: TransformImageNode
let inputActivitiesNode: ChatListInputActivitiesNode let inputActivitiesNode: ChatListInputActivitiesNode
let dateNode: TextNode let dateNode: TextNode
let separatorNode: ASDisplayNode let separatorNode: ASDisplayNode
@ -355,6 +409,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var credibilityIconNode: ASImageNode? var credibilityIconNode: ASImageNode?
let mutedIconNode: ASImageNode let mutedIconNode: ASImageNode
private var currentTextLeftCutout: CGFloat = 0.0
private var currentMediaPreviewSpecs: [(message: Message, media: Media, size: CGSize)] = []
private var mediaPreviewNodes: [MediaId: ChatListMediaPreviewNode] = [:]
var selectableControlNode: ItemListSelectableControlNode? var selectableControlNode: ItemListSelectableControlNode?
var reorderControlNode: ItemListEditableReorderControlNode? var reorderControlNode: ItemListEditableReorderControlNode?
@ -364,7 +422,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
private var cachedChatListSearchResult: CachedChatListSearchResult? private var cachedChatListSearchResult: CachedChatListSearchResult?
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)? var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
private var contentImageMedia: Media?
private var isHighlighted: Bool = false private var isHighlighted: Bool = false
private var skipFadeout: Bool = false private var skipFadeout: Bool = false
@ -423,14 +480,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
case .groupReference: case .groupReference:
return nil return nil
case let .peer(peer): case let .peer(peer):
if let message = peer.message { if let message = peer.messages.last {
var result = "" var result = ""
if message.flags.contains(.Incoming) { if message.flags.contains(.Incoming) {
result += "Message" result += "Message"
} else { } else {
result += "Outgoing message" result += "Outgoing message"
} }
let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: peer.message, chatPeer: peer.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, messages: peer.messages, chatPeer: peer.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, author is TelegramUser { if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, author is TelegramUser {
result += "\nFrom: \(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))" result += "\nFrom: \(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))"
} }
@ -473,9 +530,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.textNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = true self.textNode.displaysAsynchronously = true
self.contentImageNode = TransformImageNode()
self.contentImageNode.isHidden = true
self.inputActivitiesNode = ChatListInputActivitiesNode() self.inputActivitiesNode = ChatListInputActivitiesNode()
self.inputActivitiesNode.isUserInteractionEnabled = false self.inputActivitiesNode.isUserInteractionEnabled = false
self.inputActivitiesNode.alpha = 0.0 self.inputActivitiesNode.alpha = 0.0
@ -517,7 +571,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.contextContainer.addSubnode(self.titleNode) self.contextContainer.addSubnode(self.titleNode)
self.contextContainer.addSubnode(self.authorNode) self.contextContainer.addSubnode(self.authorNode)
self.contextContainer.addSubnode(self.textNode) self.contextContainer.addSubnode(self.textNode)
self.contextContainer.addSubnode(self.contentImageNode)
self.contextContainer.addSubnode(self.dateNode) self.contextContainer.addSubnode(self.dateNode)
self.contextContainer.addSubnode(self.statusNode) self.contextContainer.addSubnode(self.statusNode)
self.contextContainer.addSubnode(self.pinnedIconNode) self.contextContainer.addSubnode(self.pinnedIconNode)
@ -548,9 +601,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var displayAsMessage = false var displayAsMessage = false
var enablePreview = true var enablePreview = true
switch item.content { switch item.content {
case let .peer(message, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _): case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
displayAsMessage = displayAsMessageValue displayAsMessage = displayAsMessageValue
if displayAsMessage, let author = message?.author as? TelegramUser { if displayAsMessage, let author = messages.last?.author as? TelegramUser {
peer = author peer = author
} else { } else {
peer = peerValue.chatMainPeer peer = peerValue.chatMainPeer
@ -674,7 +727,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
let currentItem = self.layoutParams?.0 let currentItem = self.layoutParams?.0
let currentContentImageMedia = self.contentImageMedia
let currentChatListText = self.cachedChatListText let currentChatListText = self.cachedChatListText
let currentChatListSearchResult = self.cachedChatListSearchResult let currentChatListSearchResult = self.cachedChatListSearchResult
@ -685,7 +737,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let badgeFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let badgeFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let account = item.context.account let account = item.context.account
var message: Message? var messages: [Message]
enum ContentPeer { enum ContentPeer {
case chat(RenderedPeer) case chat(RenderedPeer)
case group([ChatListGroupReferencePeer]) case group([ChatListGroupReferencePeer])
@ -706,8 +758,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var groupHiddenByDefault = false var groupHiddenByDefault = false
switch item.content { switch item.content {
case let .peer(messageValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, hasFailedMessagesValue): case let .peer(messagesValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, hasFailedMessagesValue):
message = messageValue messages = messagesValue
contentPeer = .chat(peerValue) contentPeer = .chat(peerValue)
combinedReadState = combinedReadStateValue combinedReadState = combinedReadStateValue
if let combinedReadState = combinedReadState, promoInfoValue == nil && !ignoreUnreadBadge { if let combinedReadState = combinedReadState, promoInfoValue == nil && !ignoreUnreadBadge {
@ -729,14 +781,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
isPeerGroup = false isPeerGroup = false
promoInfo = promoInfoValue promoInfo = promoInfoValue
displayAsMessage = displayAsMessageValue displayAsMessage = displayAsMessageValue
hasFailedMessages = messageValue?.flags.contains(.Failed) ?? false // hasFailedMessagesValue hasFailedMessages = messagesValue.last?.flags.contains(.Failed) ?? false // hasFailedMessagesValue
case let .groupReference(_, peers, messageValue, unreadState, hiddenByDefault): case let .groupReference(_, peers, messageValue, unreadState, hiddenByDefault):
if let _ = messageValue, !peers.isEmpty { if let _ = messageValue, !peers.isEmpty {
contentPeer = .chat(peers[0].peer) contentPeer = .chat(peers[0].peer)
} else { } else {
contentPeer = .group(peers) contentPeer = .group(peers)
} }
message = messageValue if let message = messageValue {
messages = [message]
} else {
messages = []
}
combinedReadState = nil combinedReadState = nil
isRemovedFromTotalUnreadCount = false isRemovedFromTotalUnreadCount = false
embeddedState = nil embeddedState = nil
@ -752,10 +808,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
hasFailedMessages = false hasFailedMessages = false
} }
if let messageValue = message { if let messageValue = messages.last {
for media in messageValue.media { for media in messageValue.media {
if let media = media as? TelegramMediaAction, case .historyCleared = media.action { if let media = media as? TelegramMediaAction, case .historyCleared = media.action {
message = nil messages = []
} }
} }
} }
@ -824,7 +880,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var hideAuthor = false var hideAuthor = false
switch contentPeer { switch contentPeer {
case let .chat(itemPeer): case let .chat(itemPeer):
var (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, message: message, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) var (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup)
if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText { if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText {
initialHideAuthor = true initialHideAuthor = true
@ -843,19 +899,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var inlineAuthorPrefix: String? var inlineAuthorPrefix: String?
if case .groupReference = item.content { if case .groupReference = item.content {
if let author = message?.author as? TelegramUser { if let author = messages.last?.author as? TelegramUser {
if author.id == item.context.account.peerId { if author.id == item.context.account.peerId {
inlineAuthorPrefix = item.presentationData.strings.DialogList_You inlineAuthorPrefix = item.presentationData.strings.DialogList_You
} else if message?.id.peerId.namespace != Namespaces.Peer.CloudUser && message?.id.peerId.namespace != Namespaces.Peer.SecretChat { } else if messages.last?.id.peerId.namespace != Namespaces.Peer.CloudUser && messages.last?.id.peerId.namespace != Namespaces.Peer.SecretChat {
inlineAuthorPrefix = author.compactDisplayTitle inlineAuthorPrefix = author.compactDisplayTitle
} }
} }
} }
var contentImageMedia: Media?
var chatListText: (String, String)? var chatListText: (String, String)?
var chatListSearchResult: CachedChatListSearchResult? var chatListSearchResult: CachedChatListSearchResult?
let contentImageSize = CGSize(width: 18.0, height: 18.0)
let contentImageSpacing: CGFloat = 2.0
let contentImageTrailingSpace: CGFloat = 5.0
var contentImageSpecs: [(message: Message, media: Media, size: CGSize)] = []
switch contentData { switch contentData {
case let .chat(itemPeer, _, _, text): case let .chat(itemPeer, _, _, text):
var peerText: String? var peerText: String?
@ -863,7 +923,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let messagePeer = itemPeer.chatMainPeer { if let messagePeer = itemPeer.chatMainPeer {
peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
} }
} else if let message = message, let author = message.author as? TelegramUser, let peer = itemPeer.chatMainPeer, !(peer is TelegramUser) { } else if let message = messages.last, let author = message.author as? TelegramUser, let peer = itemPeer.chatMainPeer, !(peer is TelegramUser) {
if let peer = peer as? TelegramChannel, case .broadcast = peer.info { if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
} else if !displayAsMessage { } else if !displayAsMessage {
peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
@ -884,7 +944,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
attributedText = NSAttributedString(string: foldLineBreaks(embeddedState.text.string.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor) attributedText = NSAttributedString(string: foldLineBreaks(embeddedState.text.string.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
} else if let message = message { } else if let message = messages.last {
let composedString: NSMutableAttributedString let composedString: NSMutableAttributedString
if let inlineAuthorPrefix = inlineAuthorPrefix { if let inlineAuthorPrefix = inlineAuthorPrefix {
composedString = NSMutableAttributedString() composedString = NSMutableAttributedString()
@ -920,28 +980,52 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor) authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor)
} }
if enableChatListPhotos && !message.containsSecretMedia { var displayMediaPreviews = true
for media in message.media { if message.containsSecretMedia {
if let image = media as? TelegramMediaImage { displayMediaPreviews = false
textLeftCutout += 26.0 } else if let _ = message.peers[message.id.peerId] as? TelegramSecretChat {
contentImageMedia = image displayMediaPreviews = false
break
} else if let file = media as? TelegramMediaFile {
if file.isVideo && !file.isInstantVideo {
textLeftCutout += 26.0
contentImageMedia = file
break
} }
if !item.context.sharedContext.immediateExperimentalUISettings.chatListPhotos {
displayMediaPreviews = false
}
if displayMediaPreviews {
let contentImageFillSize = CGSize(width: 8.0, height: contentImageSize.height)
_ = contentImageFillSize
for message in messages {
inner: for media in message.media {
if let image = media as? TelegramMediaImage {
if let _ = largestImageRepresentation(image.representations) {
//let imageSize = largest.dimensions.cgSize
//let fitSize = imageSize.aspectFilled(contentImageFillSize)
let fitSize = contentImageSize
contentImageSpecs.append((message, image, fitSize))
}
break inner
} else if let file = media as? TelegramMediaFile {
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
//let imageSize = dimensions.cgSize
//let fitSize = imageSize.aspectFilled(contentImageFillSize)
let fitSize = contentImageSize
contentImageSpecs.append((message, file, fitSize))
}
break inner
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let image = content.image { let imageTypes = ["photo", "video", "embed", "gif", "document", "telegram_album"]
textLeftCutout += 26.0 if let image = content.image, let type = content.type, imageTypes.contains(type) {
contentImageMedia = image if let _ = largestImageRepresentation(image.representations) {
break //let imageSize = largest.dimensions.cgSize
let fitSize = contentImageSize
contentImageSpecs.append((message, image, fitSize))
}
break inner
} else if let file = content.file { } else if let file = content.file {
if file.isVideo && !file.isInstantVideo { if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
textLeftCutout += 26.0 //let imageSize = dimensions.cgSize
contentImageMedia = file let fitSize = contentImageSize
break contentImageSpecs.append((message, file, fitSize))
}
break inner
} }
} }
} }
@ -980,9 +1064,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
attributedText = textString attributedText = textString
} }
for i in 0 ..< contentImageSpecs.count {
if i != 0 {
textLeftCutout += contentImageSpacing
}
textLeftCutout += contentImageSpecs[i].size.width
if i == contentImageSpecs.count - 1 {
textLeftCutout += contentImageTrailingSpace
}
}
switch contentData { switch contentData {
case let .chat(itemPeer, _, _, _): case let .chat(itemPeer, _, _, _):
if let message = message, let author = message.author as? TelegramUser, displayAsMessage { if let message = messages.last, let author = message.author as? TelegramUser, displayAsMessage {
titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor) titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor)
} else if isPeerGroup { } else if isPeerGroup {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor) titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor)
@ -1024,7 +1118,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: theme.dateTextColor) dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: theme.dateTextColor)
} }
if !isPeerGroup, let message = message, message.author?.id == account.peerId && !hasDraft { if !isPeerGroup, let message = messages.last, message.author?.id == account.peerId && !hasDraft {
if message.flags.isSending && !message.isSentOrAcknowledged { if message.flags.isSending && !message.isSentOrAcknowledged {
statusState = .clock(PresentationResourcesChatList.clockFrameImage(item.presentationData.theme), PresentationResourcesChatList.clockMinImage(item.presentationData.theme)) statusState = .clock(PresentationResourcesChatList.clockFrameImage(item.presentationData.theme), PresentationResourcesChatList.clockMinImage(item.presentationData.theme))
} else if message.id.peerId != account.peerId { } else if message.id.peerId != account.peerId {
@ -1041,7 +1135,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
if unreadCount.unread { if unreadCount.unread {
if !isPeerGroup, let message = message, message.tags.contains(.unseenPersonalMessage), unreadCount.count == 1 { if !isPeerGroup, let message = messages.last, message.tags.contains(.unseenPersonalMessage), unreadCount.count == 1 {
} else { } else {
let badgeTextColor: UIColor let badgeTextColor: UIColor
if unreadCount.muted { if unreadCount.muted {
@ -1111,8 +1205,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var credibilityIconOffset: CGFloat = 0.0 var credibilityIconOffset: CGFloat = 0.0
if displayAsMessage { if displayAsMessage {
switch item.content { switch item.content {
case let .peer(message, _, _, _, _, _, _, _, _, _, _, _): case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
if let peer = message?.author { if let peer = messages.last?.author {
if peer.isScam { if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0 credibilityIconOffset = 2.0
@ -1184,7 +1278,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if !textLeftCutout.isZero { if !textLeftCutout.isZero {
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil) textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil)
} }
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize - textLeftCutout, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))) let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -1241,20 +1335,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
peerLeftRevealOptions = [] peerLeftRevealOptions = []
} }
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if let contentImageMedia = contentImageMedia {
if let currentContentImageMedia = currentContentImageMedia, contentImageMedia.isSemanticallyEqual(to: currentContentImageMedia) {
} else {
if let message = message {
if let image = contentImageMedia as? TelegramMediaImage {
updateImageSignal = mediaGridMessagePhoto(account: item.context.account, photoReference: .message(message: MessageReference(message), media: image))
} else if let file = contentImageMedia as? TelegramMediaFile {
updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true)
}
}
}
}
let (onlineLayout, onlineApply) = onlineLayout(online) let (onlineLayout, onlineApply) = onlineLayout(online)
var animateContent = false var animateContent = false
if let currentItem = currentItem, currentItem.content.chatLocation == item.content.chatLocation { if let currentItem = currentItem, currentItem.content.chatLocation == item.content.chatLocation {
@ -1287,8 +1367,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + heightOffset)), insets: insets) let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(0.0, itemHeight + heightOffset)), insets: insets)
let contentImageSize = CGSize(width: 18.0, height: 18.0)
var customActions: [ChatListItemAccessibilityCustomAction] = [] var customActions: [ChatListItemAccessibilityCustomAction] = []
for option in peerLeftRevealOptions { for option in peerLeftRevealOptions {
customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key)) customActions.append(ChatListItemAccessibilityCustomAction(name: option.title, target: nil, selector: #selector(ChatListItemNode.performLocalAccessibilityCustomAction(_:)), key: option.key))
@ -1301,42 +1379,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
strongSelf.currentItemHeight = itemHeight strongSelf.currentItemHeight = itemHeight
strongSelf.contentImageMedia = contentImageMedia
strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult strongSelf.cachedChatListSearchResult = chatListSearchResult
strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
var dimensions: CGSize?
if let contentImageMedia = contentImageMedia as? TelegramMediaImage {
dimensions = largestRepresentationForPhoto(contentImageMedia)?.dimensions.cgSize
} else if let contentImageMedia = contentImageMedia as? TelegramMediaFile {
dimensions = contentImageMedia.dimensions?.cgSize
}
var contentImageNodeAppeared = false
if let dimensions = dimensions {
let makeImageLayout = strongSelf.contentImageNode.asyncLayout()
let imageSize = contentImageSize
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
applyImageLayout()
if let updateImageSignal = updateImageSignal {
strongSelf.contentImageNode.setSignal(updateImageSignal)
if currentContentImageMedia == nil {
strongSelf.contentImageNode.isHidden = false
contentImageNodeAppeared = true
}
}
} else {
if currentContentImageMedia != nil {
strongSelf.contentImageNode.removeFromSupernode()
strongSelf.contentImageNode.setSignal(.single({ _ in nil }))
strongSelf.contentImageNode.isHidden = true
}
}
if case .groupReference = item.content { if case .groupReference = item.content {
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
} }
@ -1541,12 +1588,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.authorNode.frame = authorNodeFrame strongSelf.authorNode.frame = authorNodeFrame
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size) let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size)
strongSelf.textNode.frame = textNodeFrame strongSelf.textNode.frame = textNodeFrame
let contentImageFrame = CGRect(origin: textNodeFrame.origin.offsetBy(dx: 1.0, dy: 2.0), size: contentImageSize)
if contentImageNodeAppeared {
strongSelf.contentImageNode.frame = contentImageFrame
} else {
transition.updateFrame(node: strongSelf.contentImageNode, frame: contentImageFrame)
}
var animateInputActivitiesFrame = false var animateInputActivitiesFrame = false
if let inputActivities = inputActivities, !inputActivities.isEmpty { if let inputActivities = inputActivities, !inputActivities.isEmpty {
@ -1586,7 +1627,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
if let inputActivitiesSize = inputActivitiesSize { if let inputActivitiesSize = inputActivitiesSize {
let inputActivitiesFrame = CGRect(origin: CGPoint(x: authorNodeFrame.minX + 1.0, y: authorNodeFrame.minY + UIScreenPixel), size: inputActivitiesSize) let inputActivitiesFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: authorNodeFrame.minY + UIScreenPixel), size: inputActivitiesSize)
if animateInputActivitiesFrame { if animateInputActivitiesFrame {
transition.updateFrame(node: strongSelf.inputActivitiesNode, frame: inputActivitiesFrame) transition.updateFrame(node: strongSelf.inputActivitiesNode, frame: inputActivitiesFrame)
} else { } else {
@ -1595,6 +1636,43 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
inputActivitiesApply?() inputActivitiesApply?()
var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
var validMediaIds: [MediaId] = []
for (message, media, mediaSize) in contentImageSpecs {
guard let mediaId = media.id else {
continue
}
validMediaIds.append(mediaId)
let previewNode: ChatListMediaPreviewNode
var previewNodeTransition = transition
var previewNodeAlphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
if let current = strongSelf.mediaPreviewNodes[mediaId] {
previewNode = current
} else {
previewNodeTransition = .immediate
previewNodeAlphaTransition = .immediate
previewNode = ChatListMediaPreviewNode(context: item.context, message: message, media: media)
strongSelf.mediaPreviewNodes[mediaId] = previewNode
strongSelf.contextContainer.addSubnode(previewNode)
}
previewNode.updateLayout(size: mediaSize, synchronousLoads: synchronousLoads)
previewNodeAlphaTransition.updateAlpha(node: previewNode, alpha: strongSelf.inputActivitiesNode.alpha.isZero ? 1.0 : 0.0)
previewNodeTransition.updateFrame(node: previewNode, frame: CGRect(origin: mediaPreviewOffset, size: mediaSize))
mediaPreviewOffset.x += mediaSize.width + contentImageSpacing
}
var removeMediaIds: [MediaId] = []
for (mediaId, itemNode) in strongSelf.mediaPreviewNodes {
if !validMediaIds.contains(mediaId) {
removeMediaIds.append(mediaId)
itemNode.removeFromSupernode()
}
}
for mediaId in removeMediaIds {
strongSelf.mediaPreviewNodes.removeValue(forKey: mediaId)
}
strongSelf.currentMediaPreviewSpecs = contentImageSpecs
strongSelf.currentTextLeftCutout = textLeftCutout
if !contentDelta.x.isZero || !contentDelta.y.isZero { if !contentDelta.x.isZero || !contentDelta.y.isZero {
let titlePosition = strongSelf.titleNode.position let titlePosition = strongSelf.titleNode.position
transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y)) transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y))
@ -1749,9 +1827,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
textFrame.origin.x = contentRect.origin.x textFrame.origin.x = contentRect.origin.x
transition.updateFrameAdditive(node: self.textNode, frame: textFrame) transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
var contentImageFrame = self.contentImageNode.frame var mediaPreviewOffset = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
contentImageFrame.origin = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0) let contentImageSpacing: CGFloat = 2.0
transition.updateFrame(node: self.contentImageNode, frame: contentImageFrame) for (_, media, mediaSize) in self.currentMediaPreviewSpecs {
guard let mediaId = media.id else {
continue
}
if let previewNode = self.mediaPreviewNodes[mediaId] {
transition.updateFrame(node: previewNode, frame: CGRect(origin: mediaPreviewOffset, size: mediaSize))
}
mediaPreviewOffset.x += mediaSize.width + contentImageSpacing
}
let dateFrame = self.dateNode.frame let dateFrame = self.dateNode.frame
transition.updateFrame(node: self.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width, y: dateFrame.minY), size: dateFrame.size)) transition.updateFrame(node: self.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width, y: dateFrame.minY), size: dateFrame.size))

View File

@ -7,9 +7,44 @@ import TelegramUIPreferences
import TelegramStringFormatting import TelegramStringFormatting
import LocalizedPeerData import LocalizedPeerData
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message?, chatPeer: RenderedPeer, accountPeerId: PeerId, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: Peer?, hideAuthor: Bool, messageText: String) { private enum MessageGroupType {
case photos
case videos
case generic
}
private func singleMessageType(message: Message) -> MessageGroupType {
for media in message.media {
if let _ = media as? TelegramMediaImage {
return .photos
} else if let file = media as? TelegramMediaFile {
if file.isVideo && !file.isInstantVideo {
return .videos
}
}
}
return .generic
}
private func messageGroupType(messages: [Message]) -> MessageGroupType {
if messages.isEmpty {
return .generic
}
let currentType = singleMessageType(message: messages[0])
for i in 1 ..< messages.count {
let nextType = singleMessageType(message: messages[i])
if nextType != currentType {
return .generic
}
}
return currentType
}
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, messages: [Message], chatPeer: RenderedPeer, accountPeerId: PeerId, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: Peer?, hideAuthor: Bool, messageText: String) {
let peer: Peer? let peer: Peer?
let message = messages.last
var hideAuthor = false var hideAuthor = false
var messageText: String var messageText: String
if let message = message { if let message = message {
@ -19,8 +54,32 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
peer = chatPeer.chatMainPeer peer = chatPeer.chatMainPeer
} }
messageText = message.text messageText = messages[0].text
var textIsReady = false
if messages.count > 1 {
let groupType = messageGroupType(messages: messages)
switch groupType {
case .photos:
if !messageText.isEmpty {
textIsReady = true
} else {
messageText = strings.ChatList_MessagePhotos(Int32(messages.count))
textIsReady = true
}
case .videos:
if !messageText.isEmpty {
textIsReady = true
} else {
messageText = strings.ChatList_MessageVideos(Int32(messages.count))
textIsReady = true
}
case .generic:
break
}
}
if !textIsReady {
for media in message.media { for media in message.media {
switch media { switch media {
case _ as TelegramMediaImage: case _ as TelegramMediaImage:
@ -87,10 +146,12 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = strings.Message_Video messageText = strings.Message_Video
processed = true processed = true
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { } else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if !fileMedia.isAnimated && enableMediaEmoji { if enableMediaEmoji {
if !fileMedia.isAnimated {
messageText = "📹 \(messageText)" messageText = "📹 \(messageText)"
processed = true
} }
}
processed = true
break inner break inner
} }
} }
@ -166,6 +227,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
break break
} }
} }
}
} else { } else {
peer = chatPeer.chatMainPeer peer = chatPeer.chatMainPeer
messageText = "" messageText = ""

View File

@ -176,10 +176,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction.additionalCategorySelected(id) nodeInteraction.additionalCategorySelected(id)
} }
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
case let .PeerEntry(index, presentationData, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
switch mode { switch mode {
case .chatList: case .chatList:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(messages: messages, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .peers(filter, isSelecting, _, filters): case let .peers(filter, isSelecting, _, filters):
let itemPeer = peer.chatMainPeer let itemPeer = peer.chatMainPeer
var chatPeer: Peer? var chatPeer: Peer?
@ -290,10 +290,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
return entries.map { entry -> ListViewUpdateItem in return entries.map { entry -> ListViewUpdateItem in
switch entry.entry { switch entry.entry {
case let .PeerEntry(index, presentationData, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
switch mode { switch mode {
case .chatList: case .chatList:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(messages: messages, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .peers(filter, isSelecting, _, filters): case let .peers(filter, isSelecting, _, filters):
let itemPeer = peer.chatMainPeer let itemPeer = peer.chatMainPeer
var chatPeer: Peer? var chatPeer: Peer?

View File

@ -46,7 +46,7 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
enum ChatListNodeEntry: Comparable, Identifiable { enum ChatListNodeEntry: Comparable, Identifiable {
case HeaderEntry case HeaderEntry
case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool) case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool)
case HoleEntry(ChatListHole, theme: PresentationTheme) case HoleEntry(ChatListHole, theme: PresentationTheme)
case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool) case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool)
case ArchiveIntro(presentationData: ChatListPresentationData) case ArchiveIntro(presentationData: ChatListPresentationData)
@ -98,27 +98,33 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact): case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact):
switch rhs { switch rhs {
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact): case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact):
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
if lhsPresentationData !== rhsPresentationData { if lhsPresentationData !== rhsPresentationData {
return false return false
} }
if lhsMessage?.stableVersion != rhsMessage?.stableVersion { if lhsUnreadCount != rhsUnreadCount {
return false return false
} }
if lhsMessage?.id != rhsMessage?.id || lhsMessage?.flags != rhsMessage?.flags || lhsUnreadCount != rhsUnreadCount { if lhsMessages.count != rhsMessages.count {
return false return false
} }
if let lhsMessage = lhsMessage, let rhsMessage = rhsMessage { for i in 0 ..< lhsMessages.count {
if lhsMessage.associatedMessages.count != rhsMessage.associatedMessages.count { if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion {
return false return false
} }
for (id, message) in lhsMessage.associatedMessages { if lhsMessages[i].id != rhsMessages[i].id {
if let otherMessage = rhsMessage.associatedMessages[id] { return false
}
if lhsMessages[i].associatedMessages.count != rhsMessages[i].associatedMessages.count {
return false
}
for (id, message) in lhsMessages[i].associatedMessages {
if let otherMessage = rhsMessages[i].associatedMessages[id] {
if message.stableVersion != otherMessage.stableVersion { if message.stableVersion != otherMessage.stableVersion {
return false return false
} }
@ -295,20 +301,20 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
var filterAfterHole = false var filterAfterHole = false
loop: for entry in view.entries { loop: for entry in view.entries {
switch entry { switch entry {
case let .MessageEntry(index, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact): case let .MessageEntry(index, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId { if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId {
continue loop continue loop
} }
if state.pendingRemovalPeerIds.contains(index.messageIndex.id.peerId) { if state.pendingRemovalPeerIds.contains(index.messageIndex.id.peerId) {
continue loop continue loop
} }
var updatedMessage = message var updatedMessages = messages
var updatedCombinedReadState = combinedReadState var updatedCombinedReadState = combinedReadState
if state.pendingClearHistoryPeerIds.contains(index.messageIndex.id.peerId) { if state.pendingClearHistoryPeerIds.contains(index.messageIndex.id.peerId) {
updatedMessage = nil updatedMessages = []
updatedCombinedReadState = nil updatedCombinedReadState = nil
} }
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: hasFailed, isContact: isContact)) result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: hasFailed, isContact: isContact))
case let .HoleEntry(hole): case let .HoleEntry(hole):
if hole.index.timestamp == Int32.max - 1 { if hole.index.timestamp == Int32.max - 1 {
//return ([.HeaderEntry], true) //return ([.HeaderEntry], true)
@ -321,7 +327,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
if let savedMessagesPeer = savedMessagesPeer { if let savedMessagesPeer = savedMessagesPeer {
result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, message: nil, readState: nil, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false)) result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false))
} else { } else {
if !filteredAdditionalItemEntries.isEmpty { if !filteredAdditionalItemEntries.isEmpty {
for item in filteredAdditionalItemEntries.reversed() { for item in filteredAdditionalItemEntries.reversed() {
@ -336,8 +342,8 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
promoInfo = .psa(type: type, message: message) promoInfo = .psa(type: type, message: message)
} }
switch item.entry { switch item.entry {
case let .MessageEntry(index, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact): case let .MessageEntry(index, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, message: message, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: promoInfo, hasFailedMessages: hasFailed, isContact: isContact)) result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], promoInfo: promoInfo, hasFailedMessages: hasFailed, isContact: isContact))
if pinningIndex != 0 { if pinningIndex != 0 {
pinningIndex -= 1 pinningIndex -= 1
} }

View File

@ -21,7 +21,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_SendMessage, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_SendMessage, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let contactsController = contactsController, let navigationController = contactsController.navigationController as? NavigationController { if let contactsController = contactsController, let navigationController = contactsController.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), peekData: nil))
} }
f(.default) f(.default)
}))) })))
@ -58,7 +58,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|> deliverOnMainQueue).start(next: { currentPeerId in |> deliverOnMainQueue).start(next: { currentPeerId in
if let currentPeerId = currentPeerId { if let currentPeerId = currentPeerId {
if let contactsController = contactsController, let navigationController = (contactsController.navigationController as? NavigationController) { if let contactsController = contactsController, let navigationController = (contactsController.navigationController as? NavigationController) {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(currentPeerId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(currentPeerId), peekData: nil))
} }
} else { } else {
var createSignal = createSecretChat(account: context.account, peerId: peerId) var createSignal = createSecretChat(account: context.account, peerId: peerId)
@ -93,7 +93,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
createSecretChatDisposable.set((createSignal createSecretChatDisposable.set((createSignal
|> deliverOnMainQueue).start(next: { peerId in |> deliverOnMainQueue).start(next: { peerId in
if let navigationController = (contactsController?.navigationController as? NavigationController) { if let navigationController = (contactsController?.navigationController as? NavigationController) {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), peekData: nil))
} }
}, error: { _ in }, error: { _ in
if let contactsController = contactsController { if let contactsController = contactsController {

View File

@ -127,6 +127,12 @@ public class ImageNode: ASDisplayNode {
private var first = true private var first = true
private let enableEmpty: Bool private let enableEmpty: Bool
private let _contentReady = Promise<Bool>()
private var didSetReady: Bool = false
public var contentReady: Signal<Bool, NoError> {
return self._contentReady.get()
}
public var ready: Signal<Bool, NoError> { public var ready: Signal<Bool, NoError> {
if let hasImage = self.hasImage { if let hasImage = self.hasImage {
return hasImage.get() return hasImage.get()
@ -171,6 +177,10 @@ public class ImageNode: ASDisplayNode {
hasImage.set(true) hasImage.set(true)
} }
} }
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
strongSelf._contentReady.set(.single(true))
}
} }
} }
})) }))

View File

@ -137,6 +137,11 @@ public final class TextAlertContentNode: AlertContentNode {
private var validLayout: CGSize? private var validLayout: CGSize?
private let _dismissOnOutsideTap: Bool
override public var dismissOnOutsideTap: Bool {
return self._dismissOnOutsideTap
}
public var textAttributeAction: (NSAttributedString.Key, (Any) -> Void)? { public var textAttributeAction: (NSAttributedString.Key, (Any) -> Void)? {
didSet { didSet {
if let (attribute, textAttributeAction) = self.textAttributeAction { if let (attribute, textAttributeAction) = self.textAttributeAction {
@ -160,9 +165,10 @@ public final class TextAlertContentNode: AlertContentNode {
} }
} }
public init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout) { public init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout, dismissOnOutsideTap: Bool) {
self.theme = theme self.theme = theme
self.actionLayout = actionLayout self.actionLayout = actionLayout
self._dismissOnOutsideTap = dismissOnOutsideTap
if let title = title { if let title = title {
let titleNode = ImmediateTextNode() let titleNode = ImmediateTextNode()
titleNode.attributedText = title titleNode.attributedText = title
@ -364,11 +370,11 @@ public final class TextAlertContentNode: AlertContentNode {
} }
} }
public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController { public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, dismissOnOutsideTap: Bool = true) -> AlertController {
return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions, actionLayout: actionLayout)) return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap))
} }
public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false) -> AlertController { public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true) -> AlertController {
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let attributedText: NSAttributedString let attributedText: NSAttributedString
if parseMarkdown { if parseMarkdown {
@ -385,7 +391,7 @@ public func standardTextAlertController(theme: AlertControllerTheme, title: Stri
dismissImpl?() dismissImpl?()
action.action() action.action()
}) })
}, actionLayout: actionLayout), allowInputInset: allowInputInset) }, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap), allowInputInset: allowInputInset)
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
} }

View File

@ -905,6 +905,7 @@ public class TextNode: ASDisplayNode {
var strikethroughs: [TextNodeStrikethrough] = [] var strikethroughs: [TextNodeStrikethrough] = []
var lineConstrainedWidth = constrainedSize.width var lineConstrainedWidth = constrainedSize.width
var lineConstrainedWidthDelta: CGFloat = 0.0
var lineOriginY = floorToScreenPixels(layoutSize.height + fontAscent) var lineOriginY = floorToScreenPixels(layoutSize.height + fontAscent)
if !first { if !first {
lineOriginY += fontLineSpacing lineOriginY += fontLineSpacing
@ -915,6 +916,7 @@ public class TextNode: ASDisplayNode {
if cutoutEnabled { if cutoutEnabled {
if lineOriginY - fontLineHeight < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY { if lineOriginY - fontLineHeight < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY {
lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth) lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth)
lineConstrainedWidthDelta = -cutoutWidth
lineCutoutOffset = cutoutOffset lineCutoutOffset = cutoutOffset
lineAdditionalWidth = cutoutWidth lineAdditionalWidth = cutoutWidth
} }
@ -945,6 +947,7 @@ public class TextNode: ASDisplayNode {
let originalLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 0.0) let originalLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 0.0)
var lineConstrainedSize = constrainedSize var lineConstrainedSize = constrainedSize
lineConstrainedSize.width += lineConstrainedWidthDelta
if bottomCutoutEnabled { if bottomCutoutEnabled {
lineConstrainedSize.width -= bottomCutoutSize.width lineConstrainedSize.width -= bottomCutoutSize.width
} }

View File

@ -1168,9 +1168,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.loadProgress.set(1.0) strongSelf.loadProgress.set(1.0)
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), openPeer: { peerId, navigation in strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), openPeer: { peerId, navigation in
switch navigation { switch navigation {
case let .chat(_, subject): case let .chat(_, subject, peekData):
if let navigationController = strongSelf.getNavigationController() { if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject)) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, peekData: peekData))
} }
case let .withBotStartPayload(botStart): case let .withBotStartPayload(botStart):
if let navigationController = strongSelf.getNavigationController() { if let navigationController = strongSelf.getNavigationController() {

View File

@ -21,14 +21,14 @@ public final class JoinLinkPreviewController: ViewController {
private let context: AccountContext private let context: AccountContext
private let link: String private let link: String
private let navigateToPeer: (PeerId) -> Void private let navigateToPeer: (PeerId, ChatPeekTimeout?) -> Void
private let parentNavigationController: NavigationController? private let parentNavigationController: NavigationController?
private var resolvedState: ExternalJoiningChatState? private var resolvedState: ExternalJoiningChatState?
private var presentationData: PresentationData private var presentationData: PresentationData
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
public init(context: AccountContext, link: String, navigateToPeer: @escaping (PeerId) -> Void, parentNavigationController: NavigationController?, resolvedState: ExternalJoiningChatState? = nil) { public init(context: AccountContext, link: String, navigateToPeer: @escaping (PeerId, ChatPeekTimeout?) -> Void, parentNavigationController: NavigationController?, resolvedState: ExternalJoiningChatState? = nil) {
self.context = context self.context = context
self.link = link self.link = link
self.navigateToPeer = navigateToPeer self.navigateToPeer = navigateToPeer
@ -81,7 +81,10 @@ public final class JoinLinkPreviewController: ViewController {
let data = JoinLinkPreviewData(isGroup: participants != nil, isJoined: false) let data = JoinLinkPreviewData(isGroup: participants != nil, isJoined: false)
strongSelf.controllerNode.setPeer(image: photoRepresentation, title: title, memberCount: participantsCount, members: participants ?? [], data: data) strongSelf.controllerNode.setPeer(image: photoRepresentation, title: title, memberCount: participantsCount, members: participants ?? [], data: data)
case let .alreadyJoined(peerId): case let .alreadyJoined(peerId):
strongSelf.navigateToPeer(peerId) strongSelf.navigateToPeer(peerId, nil)
strongSelf.dismiss()
case let .peek(peerId, deadline):
strongSelf.navigateToPeer(peerId, ChatPeekTimeout(deadline: deadline, linkData: strongSelf.link))
strongSelf.dismiss() strongSelf.dismiss()
case .invalidHash: case .invalidHash:
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.GroupInfo_InvitationLinkDoesNotExist, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.GroupInfo_InvitationLinkDoesNotExist, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
@ -119,7 +122,7 @@ public final class JoinLinkPreviewController: ViewController {
self.disposable.set((joinChatInteractively(with: self.link, account: self.context.account) |> deliverOnMainQueue).start(next: { [weak self] peerId in self.disposable.set((joinChatInteractively(with: self.link, account: self.context.account) |> deliverOnMainQueue).start(next: { [weak self] peerId in
if let strongSelf = self { if let strongSelf = self {
if let peerId = peerId { if let peerId = peerId {
strongSelf.navigateToPeer(peerId) strongSelf.navigateToPeer(peerId, nil)
strongSelf.dismiss() strongSelf.dismiss()
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "ic_editor_font.pdf", "filename" : "ic_menu_brush4.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_brush2.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_brush3.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_brush1.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_brush4.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_brush2.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_brush3.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_brush1.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_font3.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_font2.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_font1.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_font3.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_font2.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_editor_font1.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -3,13 +3,18 @@
#import <LegacyComponents/TGMediaSelectionContext.h> #import <LegacyComponents/TGMediaSelectionContext.h>
@class AVURLAsset; @class AVURLAsset;
@class TGMediaAsset;
@interface TGCameraCapturedVideo : NSObject <TGMediaEditableItem, TGMediaSelectableItem> @interface TGCameraCapturedVideo : NSObject <TGMediaEditableItem, TGMediaSelectableItem>
@property (nonatomic, readonly) AVURLAsset *avAsset; @property (nonatomic, readonly) SSignal *avAsset;
@property (nonatomic, readonly) NSTimeInterval videoDuration; @property (nonatomic, readonly) NSTimeInterval videoDuration;
@property (nonatomic, readonly) bool isAnimation;
@property (nonatomic, readonly) TGMediaAsset *originalAsset;
- (instancetype)initWithURL:(NSURL *)url; - (instancetype)initWithURL:(NSURL *)url;
- (instancetype)initWithAsset:(TGMediaAsset *)asset;
- (void)_cleanUp; - (void)_cleanUp;

View File

@ -7,7 +7,7 @@
@interface TGMediaPickerGalleryVideoItem : TGMediaPickerGalleryItem <TGModernGallerySelectableItem, TGModernGalleryEditableItem> @interface TGMediaPickerGalleryVideoItem : TGMediaPickerGalleryItem <TGModernGallerySelectableItem, TGModernGalleryEditableItem>
@property (nonatomic, readonly) AVAsset *avAsset; @property (nonatomic, readonly) SSignal *avAsset;
@property (nonatomic, readonly) CGSize dimensions; @property (nonatomic, readonly) CGSize dimensions;
- (SSignal *)durationSignal; - (SSignal *)durationSignal;

View File

@ -52,7 +52,7 @@
@interface TGMediaSelectionChange : NSObject @interface TGMediaSelectionChange : NSObject
@property (nonatomic, readonly) id<TGMediaSelectableItem> item; @property (nonatomic, readonly) NSObject <TGMediaSelectableItem> *item;
@property (nonatomic, readonly) bool selected; @property (nonatomic, readonly) bool selected;
@property (nonatomic, readonly) bool animated; @property (nonatomic, readonly) bool animated;
@property (nonatomic, readonly, strong) id sender; @property (nonatomic, readonly, strong) id sender;

View File

@ -4,6 +4,12 @@
@class TGPhotoPaintEntitySelectionView; @class TGPhotoPaintEntitySelectionView;
@class TGPaintUndoManager; @class TGPaintUndoManager;
@interface UIView (PixelColor)
- (UIColor *)colorAtPoint:(CGPoint)point;
@end
@interface TGPhotoPaintEntityView : UIView @interface TGPhotoPaintEntityView : UIView
{ {
NSInteger _entityUUID; NSInteger _entityUUID;

View File

@ -4,9 +4,9 @@
@class TGPhotoPaintFont; @class TGPhotoPaintFont;
typedef enum { typedef enum {
TGPhotoPaintTextEntityStyleBorder, TGPhotoPaintTextEntityStyleOutlined,
TGPhotoPaintTextEntityStyleClassic, TGPhotoPaintTextEntityStyleRegular,
TGPhotoPaintTextEntityStyleFrame TGPhotoPaintTextEntityStyleFramed
} TGPhotoPaintTextEntityStyle; } TGPhotoPaintTextEntityStyle;
@interface TGPhotoPaintTextEntity : TGPhotoPaintEntity @interface TGPhotoPaintTextEntity : TGPhotoPaintEntity

View File

@ -270,6 +270,13 @@
GPUImageOutput *currentInput = _currentInput; GPUImageOutput *currentInput = _currentInput;
if ([currentInput isKindOfClass:[PGVideoMovie class]]) { if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
if (capture) {
if ([currentInput isKindOfClass:[PGVideoMovie class]])
[(PGVideoMovie *)currentInput process];
[_finalFilter useNextFrameForImageCapture];
if (completion != nil)
completion();
} else {
if (!_playing) { if (!_playing) {
_playing = true; _playing = true;
[_videoQueue dispatch:^{ [_videoQueue dispatch:^{
@ -278,6 +285,7 @@
} }
}]; }];
} }
}
} else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { } else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) {
if (capture) if (capture)
[_finalFilter useNextFrameForImageCapture]; [_finalFilter useNextFrameForImageCapture];

View File

@ -14,6 +14,7 @@
- (void)cancelProcessing; - (void)cancelProcessing;
- (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer; - (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer;
- (void)process;
- (void)reprocessCurrent; - (void)reprocessCurrent;
@end @end

View File

@ -250,6 +250,11 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN
[self processPixelBufferAtTime:outputItemTime]; [self processPixelBufferAtTime:outputItemTime];
} }
- (void)process {
_shouldReprocessCurrentFrame = true;
[self displayLinkCallback:displayLink];
}
- (void)processPixelBufferAtTime:(CMTime)outputItemTime - (void)processPixelBufferAtTime:(CMTime)outputItemTime
{ {
if ([playerItemOutput hasNewPixelBufferForItemTime:outputItemTime] || _shouldReprocessCurrentFrame) if ([playerItemOutput hasNewPixelBufferForItemTime:outputItemTime] || _shouldReprocessCurrentFrame)

View File

@ -178,7 +178,13 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
if (strongSelf == nil) if (strongSelf == nil)
return; return;
NSInteger index = [strongSelf->_fetchResult indexOfAsset:(TGMediaAsset *)change.item]; NSInteger index = 0;
if ([change.item isKindOfClass:[TGCameraCapturedVideo class]]) {
index = [strongSelf->_fetchResult indexOfAsset:((TGCameraCapturedVideo *)change.item).originalAsset];
} else {
index = [strongSelf->_fetchResult indexOfAsset:(TGMediaAsset *)change.item];
}
[strongSelf updateSendButtonsFromIndex:index]; [strongSelf updateSendButtonsFromIndex:index];
[strongSelf updateSelectionIndexes]; [strongSelf updateSelectionIndexes];
@ -917,7 +923,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
if ([editableItem isKindOfClass:[TGMediaAsset class]]) { if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem]; return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset]; return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else { } else {
return [editableItem originalImageSignal:position]; return [editableItem originalImageSignal:position];
} }

View File

@ -3,21 +3,49 @@
#import <LegacyComponents/TGMediaAssetImageSignals.h> #import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/TGPhotoEditorUtils.h> #import <LegacyComponents/TGPhotoEditorUtils.h>
#import "LegacyComponentsGlobals.h"
#import "TGStringUtils.h"
#import "TGMediaAsset.h"
#import "TGMediaAsset+TGMediaEditableItem.h"
#import "TGGifConverter.h"
@interface TGCameraCapturedVideo () @interface TGCameraCapturedVideo ()
{ {
CGSize _cachedSize; CGSize _cachedSize;
NSTimeInterval _cachedDuration; NSTimeInterval _cachedDuration;
AVURLAsset *_cachedAVAsset;
} }
@end @end
@implementation TGCameraCapturedVideo @implementation TGCameraCapturedVideo
+ (NSURL *)videoURLForAsset:(TGMediaAsset *)asset {
NSURL *convertedGifsUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"convertedGifs"]];
[[NSFileManager defaultManager] createDirectoryAtPath:convertedGifsUrl.path withIntermediateDirectories:true attributes:nil error:nil];
return [convertedGifsUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4", [TGStringUtils md5:asset.identifier]]];
}
- (instancetype)initWithURL:(NSURL *)url - (instancetype)initWithURL:(NSURL *)url
{ {
self = [super init]; self = [super init];
if (self != nil) if (self != nil)
{ {
_avAsset = [[AVURLAsset alloc] initWithURL:url options:nil]; _cachedAVAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
_cachedSize = CGSizeZero;
_cachedDuration = 0.0;
}
return self;
}
- (instancetype)initWithAsset:(TGMediaAsset *)asset
{
self = [super init];
if (self != nil)
{
_originalAsset = asset;
_cachedSize = CGSizeZero; _cachedSize = CGSizeZero;
_cachedDuration = 0.0; _cachedDuration = 0.0;
} }
@ -26,7 +54,9 @@
- (void)_cleanUp - (void)_cleanUp
{ {
[[NSFileManager defaultManager] removeItemAtPath:_avAsset.URL.path error:nil]; if (_originalAsset == nil) {
[[NSFileManager defaultManager] removeItemAtPath:_cachedAVAsset.URL.path error:nil];
}
} }
- (bool)isVideo - (bool)isVideo
@ -34,9 +64,47 @@
return true; return true;
} }
- (bool)isAnimation {
return _originalAsset != nil;
}
- (SSignal *)avAsset {
if (_originalAsset != nil) {
if (_cachedAVAsset != nil) {
return [SSignal single:_cachedAVAsset];
} else {
NSURL *videoUrl = [TGCameraCapturedVideo videoURLForAsset:_originalAsset];
return [[TGMediaAssetImageSignals imageDataForAsset:_originalAsset allowNetworkAccess:false] mapToSignal:^SSignal *(TGMediaAssetImageData *assetData) {
NSData *data = assetData.imageData;
const char *gif87Header = "GIF87";
const char *gif89Header = "GIF89";
if (data.length >= 5 && (!memcmp(data.bytes, gif87Header, 5) || !memcmp(data.bytes, gif89Header, 5)))
{
return [[TGGifConverter convertGifToMp4:data] map:^id(NSDictionary *result)
{
NSString *filePath = result[@"path"];
[[NSFileManager defaultManager] moveItemAtPath:filePath toPath:videoUrl.path error:nil];
return [AVURLAsset assetWithURL:videoUrl];
}];
} else {
return [SSignal complete];
}
}];
}
} else {
return [SSignal single:_cachedAVAsset];
}
}
- (NSString *)uniqueIdentifier - (NSString *)uniqueIdentifier
{ {
return _avAsset.URL.absoluteString; if (_originalAsset) {
return _originalAsset.uniqueIdentifier;
} else {
return _cachedAVAsset.URL.absoluteString;
}
} }
- (CGSize)originalSize - (CGSize)originalSize
@ -44,7 +112,11 @@
if (!CGSizeEqualToSize(_cachedSize, CGSizeZero)) if (!CGSizeEqualToSize(_cachedSize, CGSizeZero))
return _cachedSize; return _cachedSize;
AVAssetTrack *track = _avAsset.tracks.firstObject; if (_originalAsset != nil) {
return [_originalAsset originalSize];
}
AVAssetTrack *track = _cachedAVAsset.tracks.firstObject;
_cachedSize = CGRectApplyAffineTransform((CGRect){ CGPointZero, track.naturalSize }, track.preferredTransform).size; _cachedSize = CGRectApplyAffineTransform((CGRect){ CGPointZero, track.naturalSize }, track.preferredTransform).size;
return _cachedSize; return _cachedSize;
} }
@ -59,29 +131,41 @@
if (_cachedDuration > DBL_EPSILON) if (_cachedDuration > DBL_EPSILON)
return _cachedDuration; return _cachedDuration;
_cachedDuration = CMTimeGetSeconds(_avAsset.duration); if (_cachedAVAsset != nil) {
_cachedDuration = CMTimeGetSeconds(_cachedAVAsset.duration);
}
return _cachedDuration; return _cachedDuration;
} }
- (SSignal *)thumbnailImageSignal - (SSignal *)thumbnailImageSignal
{ {
if (_originalAsset != nil) {
return [_originalAsset thumbnailImageSignal];
} else {
CGFloat thumbnailImageSide = TGPhotoEditorScreenImageMaxSize().width; CGFloat thumbnailImageSide = TGPhotoEditorScreenImageMaxSize().width;
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(thumbnailImageSide, thumbnailImageSide)); CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(thumbnailImageSide, thumbnailImageSide));
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_avAsset size:size timestamp:kCMTimeZero]; return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_cachedAVAsset size:size timestamp:kCMTimeZero];
}
} }
- (SSignal *)screenImageSignal:(NSTimeInterval)__unused position - (SSignal *)screenImageSignal:(NSTimeInterval)position
{ {
if (_originalAsset != nil) {
return [_originalAsset screenImageSignal:position];
} else {
CGFloat imageSide = 1280.0f; CGFloat imageSide = 1280.0f;
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(imageSide, imageSide)); CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(imageSide, imageSide));
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_avAsset size:size timestamp:kCMTimeZero]; return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_cachedAVAsset size:size timestamp:kCMTimeZero];
}
} }
- (SSignal *)originalImageSignal:(NSTimeInterval)position - (SSignal *)originalImageSignal:(NSTimeInterval)position
{ {
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_avAsset size:self.originalSize timestamp:CMTimeMakeWithSeconds(position, NSEC_PER_SEC)]; return [[self avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:self.originalSize timestamp:CMTimeMakeWithSeconds(position, NSEC_PER_SEC)];
}];
} }
@end @end

View File

@ -1772,7 +1772,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
if ([editableItem isKindOfClass:[TGMediaAsset class]]) { if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem]; return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset]; return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else { } else {
return [editableItem originalImageSignal:position]; return [editableItem originalImageSignal:position];
} }
@ -2467,7 +2467,9 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
else if ([item isKindOfClass:[TGCameraCapturedVideo class]]) else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
{ {
TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)item; TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)item;
return [SSignal single:@{@"type": @"video", @"url": video.avAsset.URL}]; return [[video avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
return [SSignal single:@{@"type": @"video", @"url": avAsset.URL}];
}];
} }
return [SSignal complete]; return [SSignal complete];
@ -2689,10 +2691,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
}; };
CGSize imageSize = TGFillSize(asset.originalSize, CGSizeMake(512, 512)); CGSize imageSize = TGFillSize(asset.originalSize, CGSizeMake(512, 512));
SSignal *trimmedVideoThumbnailSignal = [[TGMediaAssetImageSignals videoThumbnailForAVAsset:video.avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image) SSignal *trimmedVideoThumbnailSignal = [[video avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image)
{ {
return cropVideoThumbnail(image, TGScaleToFill(asset.originalSize, CGSizeMake(512, 512)), asset.originalSize, true); return cropVideoThumbnail(image, TGScaleToFill(asset.originalSize, CGSizeMake(512, 512)), asset.originalSize, true);
}]; }];
}];
SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image) SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image)
{ {
@ -2709,7 +2713,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
{ {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict[@"type"] = @"cameraVideo"; dict[@"type"] = @"cameraVideo";
dict[@"url"] = video.avAsset.URL; // dict[@"url"] = video.avAsset.URL;
dict[@"previewImage"] = image; dict[@"previewImage"] = image;
dict[@"adjustments"] = adjustments; dict[@"adjustments"] = adjustments;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions]; dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];

View File

@ -1026,7 +1026,7 @@
if ([editableItem isKindOfClass:[TGMediaAsset class]]) { if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem]; return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset]; return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else { } else {
return [editableItem originalImageSignal:position]; return [editableItem originalImageSignal:position];
} }

View File

@ -38,7 +38,7 @@
- (TGPhotoEditorTab)toolbarTabs - (TGPhotoEditorTab)toolbarTabs
{ {
return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab | TGPhotoEditorTimerTab; return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab;
} }

View File

@ -77,7 +77,11 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
return; return;
} }
CGSize targetSize = TGFitSizeF(CGSizeMake(sourceWidth, sourceHeight), CGSizeMake(TGGifConverterMaximumSide, TGGifConverterMaximumSide)); const CGFloat blockSize = 16.0f;
CGFloat renderWidth = CGFloor(sourceWidth / blockSize) * blockSize;
CGFloat renderHeight = CGFloor(sourceHeight * renderWidth / sourceWidth);
CGSize targetSize = TGFitSizeF(CGSizeMake(renderWidth, renderHeight), CGSizeMake(TGGifConverterMaximumSide, TGGifConverterMaximumSide));
NSDictionary *videoCleanApertureSettings = @ NSDictionary *videoCleanApertureSettings = @
{ {
@ -122,8 +126,8 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
NSDictionary *attributes = @ NSDictionary *attributes = @
{ {
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32ARGB), (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32ARGB),
(NSString *)kCVPixelBufferWidthKey : @(sourceWidth), (NSString *)kCVPixelBufferWidthKey : @(renderWidth),
(NSString *)kCVPixelBufferHeightKey : @(sourceHeight), (NSString *)kCVPixelBufferHeightKey : @(renderHeight),
(NSString *)kCVPixelBufferCGImageCompatibilityKey : @YES, (NSString *)kCVPixelBufferCGImageCompatibilityKey : @YES,
(NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES
}; };
@ -148,7 +152,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
if (gifProperties != NULL) if (gifProperties != NULL)
{ {
CVPixelBufferRef pxBuffer = [self newBufferFrom:imgRef withPixelBufferPool:adaptor.pixelBufferPool andAttributes:adaptor.sourcePixelBufferAttributes]; CVPixelBufferRef pxBuffer = [self newBufferFrom:imgRef size:targetSize withPixelBufferPool:adaptor.pixelBufferPool andAttributes:adaptor.sourcePixelBufferAttributes];
if (pxBuffer != NULL) if (pxBuffer != NULL)
{ {
if (previewImage == nil) { if (previewImage == nil) {
@ -231,13 +235,10 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
}]; }];
}; };
+ (CVPixelBufferRef)newBufferFrom:(CGImageRef)frame withPixelBufferPool:(CVPixelBufferPoolRef)pixelBufferPool andAttributes:(NSDictionary *)attributes + (CVPixelBufferRef)newBufferFrom:(CGImageRef)frame size:(CGSize)size withPixelBufferPool:(CVPixelBufferPoolRef)pixelBufferPool andAttributes:(NSDictionary *)attributes
{ {
NSParameterAssert(frame); NSParameterAssert(frame);
size_t width = CGImageGetWidth(frame);
size_t height = CGImageGetHeight(frame);
size_t bpc = 8;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CVPixelBufferRef pxBuffer = NULL; CVPixelBufferRef pxBuffer = NULL;
@ -246,7 +247,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
if (pixelBufferPool) if (pixelBufferPool)
status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pxBuffer); status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pxBuffer);
else else
status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef)attributes, &pxBuffer); status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef)attributes, &pxBuffer);
NSAssert(status == kCVReturnSuccess, @"Could not create a pixel buffer"); NSAssert(status == kCVReturnSuccess, @"Could not create a pixel buffer");
@ -255,10 +256,10 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pxBuffer); size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pxBuffer);
CGContextRef context = CGBitmapContextCreate(pxData, width, height, bpc, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst); CGContextRef context = CGBitmapContextCreate(pxData, size.width, size.height, 8, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst);
NSAssert(context, @"Could not create a context"); NSAssert(context, @"Could not create a context");
CGContextDrawImage(context, CGRectMake(0, 0, width, height), frame); CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), frame);
CVPixelBufferUnlockBaseAddress(pxBuffer, 0); CVPixelBufferUnlockBaseAddress(pxBuffer, 0);

View File

@ -434,7 +434,7 @@
if ([editableItem isKindOfClass:[TGMediaAsset class]]) { if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem]; return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset]; return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else { } else {
return [editableItem originalImageSignal:position]; return [editableItem originalImageSignal:position];
} }

View File

@ -21,6 +21,7 @@
#import "TGModernGallerySelectableItem.h" #import "TGModernGallerySelectableItem.h"
#import "TGModernGalleryEditableItem.h" #import "TGModernGalleryEditableItem.h"
#import "TGMediaPickerGalleryPhotoItem.h" #import "TGMediaPickerGalleryPhotoItem.h"
#import "TGMediaPickerGalleryVideoItem.h"
#import "TGMediaPickerGalleryPhotoItemView.h" #import "TGMediaPickerGalleryPhotoItemView.h"
#import "TGMediaPickerGalleryVideoItemView.h" #import "TGMediaPickerGalleryVideoItemView.h"
@ -150,6 +151,8 @@
[strongSelf.window endEditing:true]; [strongSelf.window endEditing:true];
if (strongSelf->_doneLongPressed != nil) if (strongSelf->_doneLongPressed != nil)
strongSelf->_doneLongPressed(strongSelf->_currentItem); strongSelf->_doneLongPressed(strongSelf->_currentItem);
[[NSUserDefaults standardUserDefaults] setObject:@(3) forKey:@"TG_displayedMediaTimerTooltip_v3"];
}; };
_muteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 39.0f, 39.0f)]; _muteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 39.0f, 39.0f)];
@ -571,7 +574,15 @@
[strongSelf->_portraitToolbarView setEditButtonsEnabled:available animated:true]; [strongSelf->_portraitToolbarView setEditButtonsEnabled:available animated:true];
[strongSelf->_landscapeToolbarView setEditButtonsEnabled:available animated:true]; [strongSelf->_landscapeToolbarView setEditButtonsEnabled:available animated:true];
bool sendableAsGif = !strongSelf->_inhibitMute && [strongItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]]; bool sendableAsGif = !strongSelf->_inhibitMute && [strongItemView isKindOfClass:[TGMediaPickerGalleryVideoItemView class]];
if ([strongSelf->_currentItem isKindOfClass:[TGMediaPickerGalleryVideoItem class]]) {
TGMediaPickerGalleryVideoItem *item = (TGMediaPickerGalleryVideoItem *)strongSelf->_currentItem;
if ([item.asset isKindOfClass:[TGCameraCapturedVideo class]] && ((TGCameraCapturedVideo *)item.asset).isAnimation) {
sendableAsGif = false;
}
}
strongSelf->_muteButton.hidden = !sendableAsGif; strongSelf->_muteButton.hidden = !sendableAsGif;
} }
}]]; }]];
@ -911,7 +922,7 @@
- (bool)shouldDisplayTooltip - (bool)shouldDisplayTooltip
{ {
return ![[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedMediaTimerTooltip_v2"] boolValue]; return [[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedMediaTimerTooltip_v3"] intValue] < 3;
} }
- (void)setupTooltip:(CGRect)rect - (void)setupTooltip:(CGRect)rect
@ -919,7 +930,7 @@
if (_tooltipContainerView != nil || !_hasTimer) if (_tooltipContainerView != nil || !_hasTimer)
return; return;
_tooltipTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(tooltipTimerTick) interval:2.5 repeat:false]; _tooltipTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(tooltipTimerTick) interval:3.5 repeat:false];
_tooltipContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)]; _tooltipContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)];
[self addSubview:_tooltipContainerView]; [self addSubview:_tooltipContainerView];
@ -934,7 +945,8 @@
[_tooltipContainerView showMenuFromRect:rect animated:false]; [_tooltipContainerView showMenuFromRect:rect animated:false];
[[NSUserDefaults standardUserDefaults] setObject:@true forKey:@"TG_displayedMediaTimerTooltip_v2"]; int counter = [[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedMediaTimerTooltip_v3"] intValue];
[[NSUserDefaults standardUserDefaults] setObject:@(counter + 1) forKey:@"TG_displayedMediaTimerTooltip_v3"];
} }
- (void)tooltipTimerTick - (void)tooltipTimerTick

View File

@ -575,7 +575,7 @@
if ([editableItem isKindOfClass:[TGMediaAsset class]]) { if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem]; return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset]; return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else { } else {
return [editableItem originalImageSignal:position]; return [editableItem originalImageSignal:position];
} }

View File

@ -1,7 +1,5 @@
#import "TGMediaPickerGalleryPhotoItemView.h" #import "TGMediaPickerGalleryPhotoItemView.h"
#import <PhotosUI/PhotosUI.h>
#import "LegacyComponentsInternal.h" #import "LegacyComponentsInternal.h"
#import "TGFont.h" #import "TGFont.h"
#import "TGStringUtils.h" #import "TGStringUtils.h"
@ -34,7 +32,6 @@
void (^_currentAvailabilityObserver)(bool); void (^_currentAvailabilityObserver)(bool);
UIView *_temporaryRepView; UIView *_temporaryRepView;
PHLivePhotoView *_livePhotoView;
UIView *_contentView; UIView *_contentView;
UIView *_contentWrapperView; UIView *_contentWrapperView;
@ -116,11 +113,6 @@
{ {
_imageView.hidden = false; _imageView.hidden = false;
[_imageView reset]; [_imageView reset];
if (_livePhotoView != nil)
{
[_livePhotoView removeFromSuperview];
_livePhotoView = nil;
}
[self setProgressVisible:false value:0.0f animated:false]; [self setProgressVisible:false value:0.0f animated:false];
} }
@ -232,7 +224,6 @@
[strongSelf reset]; [strongSelf reset];
strongSelf->_livePhotoView.frame = strongSelf->_imageView.frame;
}]]; }]];
if (!item.asFile) if (!item.asFile)

View File

@ -50,9 +50,7 @@
return; return;
if (next.selected) if (next.selected)
{
[strongSelf addSelectedItem:next.item]; [strongSelf addSelectedItem:next.item];
}
else if (!strongSelf->_keepItems) else if (!strongSelf->_keepItems)
[strongSelf removeSelectedItem:next.item]; [strongSelf removeSelectedItem:next.item];
}]]; }]];

View File

@ -25,7 +25,7 @@
return CGSizeZero; return CGSizeZero;
} }
- (AVAsset *)avAsset - (SSignal *)avAsset
{ {
if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]]) if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]])
return ((TGCameraCapturedVideo *)self.asset).avAsset; return ((TGCameraCapturedVideo *)self.asset).avAsset;
@ -70,10 +70,13 @@
- (TGPhotoEditorTab)toolbarTabs - (TGPhotoEditorTab)toolbarTabs
{ {
if ([self.asset isKindOfClass:[TGMediaAsset class]] && ((TGMediaAsset *)self.asset).subtypes & TGMediaAssetSubtypePhotoLive) if ([self.asset isKindOfClass:[TGMediaAsset class]] && ((TGMediaAsset *)self.asset).subtypes & TGMediaAssetSubtypePhotoLive) {
return TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab | TGPhotoEditorTimerTab; return TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab;
else } else if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]] && ((TGCameraCapturedVideo *)self.asset).isAnimation) {
return TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab;
} else {
return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab | TGPhotoEditorQualityTab; return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab | TGPhotoEditorQualityTab;
}
} }
- (Class)viewClass - (Class)viewClass

View File

@ -1045,16 +1045,16 @@
if (_videoView != nil) if (_videoView != nil)
{ {
SMetaDisposable *currentAudioSession = _currentAudioSession; SMetaDisposable *currentAudioSession = _currentAudioSession;
// if (currentAudioSession) if (currentAudioSession)
// { {
// _videoView.deallocBlock = ^ // _videoView.deallocBlock = ^
// { // {
// [[SQueue concurrentDefaultQueue] dispatch:^ [[SQueue concurrentDefaultQueue] dispatch:^
// { {
// [currentAudioSession setDisposable:nil]; [currentAudioSession setDisposable:nil];
// }]; }];
// }; // };
// } }
// [_videoView cleanupPlayer]; // [_videoView cleanupPlayer];
_photoEditor.previewOutput = nil; _photoEditor.previewOutput = nil;
@ -1092,10 +1092,14 @@
[self inhibitVolumeOverlay]; [self inhibitVolumeOverlay];
SSignal *itemSignal = nil; SSignal *itemSignal = nil;
if ([self.item.asset isKindOfClass:[TGMediaAsset class]]) if ([self.item.asset isKindOfClass:[TGMediaAsset class]]) {
itemSignal = [TGMediaAssetImageSignals playerItemForVideoAsset:(TGMediaAsset *)self.item.asset]; itemSignal = [TGMediaAssetImageSignals playerItemForVideoAsset:(TGMediaAsset *)self.item.asset];
else if (self.item.avAsset != nil) }
itemSignal = [SSignal single:[AVPlayerItem playerItemWithAsset:self.item.avAsset]]; else if (self.item.avAsset != nil) {
itemSignal = [self.item.avAsset mapToSignal:^SSignal *(AVAsset *avAsset) {
return [SSignal single:[AVPlayerItem playerItemWithAsset:avAsset]];
}];
}
[_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem) [_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem)
{ {
@ -1546,7 +1550,7 @@
if (timestamps.count == 0) if (timestamps.count == 0)
return; return;
AVAsset *avAsset = self.item.avAsset ?: _player.currentItem.asset; SSignal *avAsset = self.item.avAsset ?: [SSignal single:_player.currentItem.asset];
TGMediaEditingContext *editingContext = self.item.editingContext; TGMediaEditingContext *editingContext = self.item.editingContext;
id<TGMediaEditableItem> editableItem = self.item.editableMediaItem; id<TGMediaEditableItem> editableItem = self.item.editableMediaItem;
@ -1554,7 +1558,9 @@
if ([self.item.asset isKindOfClass:[TGMediaAsset class]] && ![self itemIsLivePhoto]) if ([self.item.asset isKindOfClass:[TGMediaAsset class]] && ![self itemIsLivePhoto])
thumbnailsSignal = [TGMediaAssetImageSignals videoThumbnailsForAsset:self.item.asset size:size timestamps:timestamps]; thumbnailsSignal = [TGMediaAssetImageSignals videoThumbnailsForAsset:self.item.asset size:size timestamps:timestamps];
else if (avAsset != nil) else if (avAsset != nil)
thumbnailsSignal = [TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps]; thumbnailsSignal = [avAsset mapToSignal:^SSignal *(AVAsset *avAsset) {
return [TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps];
}];
_requestingThumbnails = true; _requestingThumbnails = true;

View File

@ -370,12 +370,17 @@
case TGMediaAssetGifType: case TGMediaAssetGifType:
{ {
// TGCameraCapturedVideo *convertedAsset = [[TGCameraCapturedVideo alloc] initWithAsset:asset];
// galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:convertedAsset];
galleryItem = [[TGMediaPickerGalleryGifItem alloc] initWithAsset:asset]; galleryItem = [[TGMediaPickerGalleryGifItem alloc] initWithAsset:asset];
} }
break; break;
default: default:
{ {
if (asset.subtypes & TGMediaAssetSubtypePhotoLive)
galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:asset];
else
galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:asset]; galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:asset];
} }
break; break;

View File

@ -68,7 +68,7 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
_countLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -0.5f, frame.size.width + 1.0, frame.size.height)]; _countLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -0.5f, frame.size.width + 1.0, frame.size.height)];
_countLabel.backgroundColor = [UIColor clearColor]; _countLabel.backgroundColor = [UIColor clearColor];
_countLabel.font = [TGFont roundedFontOfSize:17]; _countLabel.font = [TGFont roundedFontOfSize:18];
_countLabel.text = [TGStringUtils stringWithLocalizedNumber:0]; _countLabel.text = [TGStringUtils stringWithLocalizedNumber:0];
_countLabel.textColor = [UIColor whiteColor]; _countLabel.textColor = [UIColor whiteColor];
[_wrapperView addSubview:_countLabel]; [_wrapperView addSubview:_countLabel];
@ -299,15 +299,15 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
{ {
labelOrigin = 12 + TGScreenPixel + (38 - labelWidth) / 2; labelOrigin = 12 + TGScreenPixel + (38 - labelWidth) / 2;
if ([_countLabel.text isEqualToString:@"1"] || [_countLabel.text isEqualToString:@"4"]) // if ([_countLabel.text isEqualToString:@"1"] || [_countLabel.text isEqualToString:@"4"])
labelOrigin -= 2 * TGScreenPixel; // labelOrigin -= 2 * TGScreenPixel;
} }
else else
{ {
labelOrigin = (processingLabelWidth > 0) ? -processingLabelWidth + 19 + 13 - 4.5f: 64 - 38 + (38 - labelWidth) / 2.0f - 13; labelOrigin = (processingLabelWidth > 0) ? -processingLabelWidth + 19 + 13 - 4.5f: 64 - 38 + (38 - labelWidth) / 2.0f - 13;
} }
_countLabel.frame = CGRectMake(labelOrigin, 6.0f, labelWidth, _countLabel.frame.size.height); _countLabel.frame = CGRectMake(labelOrigin, 5.0 + TGScreenPixel, labelWidth, _countLabel.frame.size.height);
_countLabel.transform = transform; _countLabel.transform = transform;
} }

View File

@ -0,0 +1,5 @@
#import "TGPaintBrush.h"
@interface TGPaintArrowBrush : TGPaintBrush
@end

View File

@ -0,0 +1,90 @@
#import "TGPaintArrowBrush.h"
const CGFloat TGPaintArrowBrushHardness = 0.92f;
@implementation TGPaintArrowBrush
- (CGFloat)spacing
{
return 0.15f;
}
- (CGFloat)alpha
{
return 0.85f;
}
- (CGFloat)angle
{
return 0.0f;
}
//- (CGFloat)dynamic
//{
// return 0.75f;
//}
- (bool)arrow
{
return true;
}
- (CGImageRef)generateRadialStampForSize:(CGSize)size hardness:(CGFloat)hardness
{
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
CGContextRef ctx = CGBitmapContextCreate(NULL, (NSInteger)size.width, (NSInteger)size.height, 8, (NSInteger)size.width, colorspace, kCGImageAlphaNone);
CGContextSetGrayFillColor(ctx, 0.0f, 1.0f);
CGContextFillRect(ctx, CGRectMake(0, 0, size.width, size.height));
NSArray *colors = @[(__bridge id) [UIColor whiteColor].CGColor, (__bridge id) [UIColor blackColor].CGColor];
const CGFloat locations[] = {0.0, 1.0};
CGGradientRef gradientRef = CGGradientCreateWithColors(colorspace, (__bridge CFArrayRef) colors, locations);
CGPoint center = CGPointMake(size.width / 2, size.height / 2);
CGFloat maxRadius = size.width / 2;
CGFloat hFactor = hardness * 0.99;
CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation |kCGGradientDrawsAfterEndLocation;
CGContextDrawRadialGradient(ctx, gradientRef, center, hFactor * maxRadius, center, maxRadius, options);
CGImageRef image = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
CGColorSpaceRelease(colorspace);
CGGradientRelease(gradientRef);
return image;
}
- (CGImageRef)stampRef
{
static CGImageRef image = NULL;
if (image == NULL)
image = [self generateRadialStampForSize:TGPaintBrushTextureSize hardness:TGPaintArrowBrushHardness];
return image;
}
- (CGImageRef)previewStampRef
{
if (_previewStampRef == NULL)
_previewStampRef = [self generateRadialStampForSize:TGPaintBrushPreviewTextureSize hardness:TGPaintArrowBrushHardness];
return _previewStampRef;
}
static UIImage *radialBrushPreviewImage = nil;
- (UIImage *)previewImage
{
return radialBrushPreviewImage;
}
- (void)setPreviewImage:(UIImage *)previewImage
{
radialBrushPreviewImage = previewImage;
}
@end

View File

@ -10,7 +10,9 @@
@property (nonatomic, readonly) CGFloat alpha; @property (nonatomic, readonly) CGFloat alpha;
@property (nonatomic, readonly) CGFloat angle; @property (nonatomic, readonly) CGFloat angle;
@property (nonatomic, readonly) CGFloat scale; @property (nonatomic, readonly) CGFloat scale;
@property (nonatomic, readonly) CGFloat dynamic;
@property (nonatomic, readonly) bool lightSaber; @property (nonatomic, readonly) bool lightSaber;
@property (nonatomic, readonly) bool arrow;
@property (nonatomic, readonly) CGImageRef stampRef; @property (nonatomic, readonly) CGImageRef stampRef;
@property (nonatomic, readonly) CGImageRef previewStampRef; @property (nonatomic, readonly) CGImageRef previewStampRef;

View File

@ -61,6 +61,11 @@ const CGSize TGPaintBrushPreviewTextureSize = { 64.0f, 64.0f };
return 1.0f; return 1.0f;
} }
- (CGFloat)dynamic
{
return 0.0f;
}
- (bool)lightSaber - (bool)lightSaber
{ {
return false; return false;

View File

@ -283,6 +283,7 @@ const NSUInteger TGPaintBrushPreviewSegmentsCount = 100;
[self _setupBrush]; [self _setupBrush];
[_renderState reset]; [_renderState reset];
_path.remainder = 0.0f; _path.remainder = 0.0f;
_path.pressureRemainder = 0.0f;
_path.brush = brush; _path.brush = brush;
[TGPaintRender renderPath:_path renderState:_renderState]; [TGPaintRender renderPath:_path renderState:_renderState];

View File

@ -9,6 +9,7 @@
#import "TGPaintPath.h" #import "TGPaintPath.h"
#import "TGPaintState.h" #import "TGPaintState.h"
#import "TGPaintCanvas.h" #import "TGPaintCanvas.h"
#import "TGPaintBrush.h"
#import <LegacyComponents/TGPaintUtils.h> #import <LegacyComponents/TGPaintUtils.h>
@interface TGPaintInput () @interface TGPaintInput ()
@ -19,6 +20,8 @@
CGPoint _lastLocation; CGPoint _lastLocation;
CGFloat _lastRemainder; CGFloat _lastRemainder;
CGFloat _lastPressureRemainder;
CGFloat _lastAngle;
TGPaintPoint *_points[3]; TGPaintPoint *_points[3];
NSInteger _pointsCount; NSInteger _pointsCount;
@ -48,7 +51,7 @@
CGPoint midPoint1 = TGPaintMultiplyPoint(TGPaintAddPoints(prev1.CGPoint, prev2.CGPoint), 0.5f); CGPoint midPoint1 = TGPaintMultiplyPoint(TGPaintAddPoints(prev1.CGPoint, prev2.CGPoint), 0.5f);
CGFloat midPressure1 = (prev1.z + prev2.z) * 0.5f; CGFloat midPressure1 = (prev1.z + prev2.z) * 0.5f;
CGPoint midPoint2 = TGPaintMultiplyPoint(TGPaintAddPoints(cur.CGPoint, prev1.CGPoint), 0.5f); CGPoint midPoint2 = TGPaintMultiplyPoint(TGPaintAddPoints(cur.CGPoint, prev1.CGPoint), 0.5f);
CGFloat midPressure2 = (cur.z + prev2.z) * 0.5f; CGFloat midPressure2 = (cur.z + prev1.z) * 0.5f;
NSInteger segmentDistance = 2; NSInteger segmentDistance = 2;
CGFloat distance = TGPaintDistance(midPoint1, midPoint2); CGFloat distance = TGPaintDistance(midPoint1, midPoint2);
@ -126,12 +129,15 @@
if (_pointsCount != 0) if (_pointsCount != 0)
pressure = (pressure + _points[_pointsCount - 1].z) / 2.0f; pressure = (pressure + _points[_pointsCount - 1].z) / 2.0f;
pressure = 1.0f;
TGPaintPoint *point = [TGPaintPoint pointWithX:location.x y:location.y z:pressure]; TGPaintPoint *point = [TGPaintPoint pointWithX:location.x y:location.y z:pressure];
_points[_pointsCount++] = point; _points[_pointsCount++] = point;
if (_pointsCount == 3) if (_pointsCount == 3)
{ {
CGPoint prev = _points[1].CGPoint;
CGPoint cur = _points[2].CGPoint;
_lastAngle = atan2(cur.y - prev.y, cur.x - prev.x);
[self smoothenAndPaintPoints:canvas ended:false]; [self smoothenAndPaintPoints:canvas ended:false];
_moved = true; _moved = true;
} }
@ -156,6 +162,22 @@
else else
{ {
[self smoothenAndPaintPoints:canvas ended:true]; [self smoothenAndPaintPoints:canvas ended:true];
if (canvas.state.brush.arrow) {
CGFloat arrowLength = canvas.state.weight * 4.5;
CGFloat angle = _lastAngle;
TGPaintPoint *tip = [TGPaintPoint pointWithX:location.x y:location.y z:0.8];
TGPaintPoint *leftTip = [TGPaintPoint pointWithX:location.x + cos(angle - M_PI_4 * 3) * arrowLength y:location.y + sin(angle - M_PI_4 * 3.2) * arrowLength z:1.0];
leftTip.edge = true;
TGPaintPath *left = [[TGPaintPath alloc] initWithPoints:@[tip, leftTip]];
[self paintPath:left inCanvas:canvas];
TGPaintPoint *rightTip = [TGPaintPoint pointWithX:location.x + cos(angle + M_PI_4 * 3) * arrowLength y:location.y + sin(angle + M_PI_4 * 3.2) * arrowLength z:1.0];
rightTip.edge = true;
TGPaintPath *right = [[TGPaintPath alloc] initWithPoints:@[tip, rightTip]];
[self paintPath:right inCanvas:canvas];
}
} }
_pointsCount = 0; _pointsCount = 0;
@ -181,16 +203,20 @@
path.brush = canvas.state.brush; path.brush = canvas.state.brush;
path.baseWeight = canvas.state.weight; path.baseWeight = canvas.state.weight;
if (_clearBuffer) if (_clearBuffer) {
_lastRemainder = 0.0f; _lastRemainder = 0.0f;
_lastPressureRemainder = 0.0f;
}
path.remainder = _lastRemainder; path.remainder = _lastRemainder;
path.pressureRemainder = _lastPressureRemainder;
[canvas.painting paintStroke:path clearBuffer:_clearBuffer completion:^ [canvas.painting paintStroke:path clearBuffer:_clearBuffer completion:^
{ {
TGDispatchOnMainThread(^ TGDispatchOnMainThread(^
{ {
_lastRemainder = path.remainder; _lastRemainder = path.remainder;
_lastPressureRemainder = path.pressureRemainder;
_clearBuffer = false; _clearBuffer = false;
}); });
}]; }];

View File

@ -43,6 +43,7 @@ typedef enum
@property (nonatomic, strong) TGPaintBrush *brush; @property (nonatomic, strong) TGPaintBrush *brush;
@property (nonatomic, assign) CGFloat remainder; @property (nonatomic, assign) CGFloat remainder;
@property (nonatomic, assign) CGFloat pressureRemainder;
- (instancetype)initWithPoint:(TGPaintPoint *)point; - (instancetype)initWithPoint:(TGPaintPoint *)point;
- (instancetype)initWithPoints:(NSArray *)points; - (instancetype)initWithPoints:(NSArray *)points;

View File

@ -19,6 +19,11 @@ const CGFloat TGPaintRadialBrushHardness = 0.92f;
return 0.0f; return 0.0f;
} }
//- (CGFloat)dynamic
//{
// return 0.75f;
//}
- (CGImageRef)generateRadialStampForSize:(CGSize)size hardness:(CGFloat)hardness - (CGImageRef)generateRadialStampForSize:(CGSize)size hardness:(CGFloat)hardness
{ {
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray(); CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();

View File

@ -14,6 +14,7 @@ const NSInteger TGPaintRenderStateDefaultSize = 256;
} }
@property (nonatomic, assign) CGFloat brushWeight; @property (nonatomic, assign) CGFloat brushWeight;
@property (nonatomic, assign) CGFloat brushDynamic;
@property (nonatomic, assign) CGFloat spacing; @property (nonatomic, assign) CGFloat spacing;
@property (nonatomic, assign) CGFloat alpha; @property (nonatomic, assign) CGFloat alpha;
@property (nonatomic, assign) CGFloat angle; @property (nonatomic, assign) CGFloat angle;
@ -23,6 +24,7 @@ const NSInteger TGPaintRenderStateDefaultSize = 256;
@property (nonatomic, readonly) NSUInteger count; @property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, assign) CGFloat remainder; @property (nonatomic, assign) CGFloat remainder;
@property (nonatomic, assign) CGFloat pressureRemainder;
- (void)reset; - (void)reset;
@ -103,6 +105,7 @@ const NSInteger TGPaintRenderStateDefaultSize = 256;
} }
_remainder = 0; _remainder = 0;
_pressureRemainder = 0;
} }
@end @end
@ -120,18 +123,15 @@ typedef struct
+ (void)_paintStamp:(TGPaintPoint *)point state:(TGPaintRenderState *)state + (void)_paintStamp:(TGPaintPoint *)point state:(TGPaintRenderState *)state
{ {
CGFloat weight = state.brushWeight; CGFloat brushSize = state.brushWeight * state.scale;
CGPoint start = point.CGPoint;
CGFloat brushSize = weight;
CGFloat rotationalScatter = 0.0f;
CGFloat angleOffset = fabs(state.angle) > FLT_EPSILON ? state.angle : 0.0f; CGFloat angleOffset = fabs(state.angle) > FLT_EPSILON ? state.angle : 0.0f;
CGFloat alpha = MIN(1.0f, state.alpha * 1.55f); CGFloat alpha = MIN(1.0f, state.alpha * 1.55f);
[state prepare]; [state prepare];
[state appendValuesCount:1]; [state appendValuesCount:4];
[state addPoint:start size:brushSize angle:rotationalScatter + angleOffset alpha:alpha index:0]; for (NSInteger i = 0; i < 4; i++) {
[state addPoint:point.CGPoint size:brushSize angle:angleOffset alpha:alpha index:i];
}
} }
+ (void)_paintFromPoint:(TGPaintPoint *)lastLocation toPoint:(TGPaintPoint *)location state:(TGPaintRenderState *)state + (void)_paintFromPoint:(TGPaintPoint *)lastLocation toPoint:(TGPaintPoint *)location state:(TGPaintRenderState *)state
@ -139,6 +139,7 @@ typedef struct
CGFloat lastP = lastLocation.z; CGFloat lastP = lastLocation.z;
CGFloat p = location.z; CGFloat p = location.z;
CGFloat pDelta = p - lastP; CGFloat pDelta = p - lastP;
CGFloat pChange = 0.0f;
CGFloat f, distance = TGPaintDistance(lastLocation.CGPoint, location.CGPoint); CGFloat f, distance = TGPaintDistance(lastLocation.CGPoint, location.CGPoint);
CGPoint vector = TGPaintSubtractPoints(location.CGPoint, lastLocation.CGPoint); CGPoint vector = TGPaintSubtractPoints(location.CGPoint, lastLocation.CGPoint);
@ -148,8 +149,8 @@ typedef struct
CGFloat brushWeight = state.brushWeight * state.scale; CGFloat brushWeight = state.brushWeight * state.scale;
CGFloat step = MAX(1.0f, state.spacing * brushWeight); CGFloat step = MAX(1.0f, state.spacing * brushWeight);
CGFloat pressure = lastP; CGFloat pressure = lastP + state.pressureRemainder;
CGFloat pressureStep = 0.0f; CGFloat pressureStep = pressureStep = pDelta / ((distance - state.remainder) / step);
if (distance > 0.0f) if (distance > 0.0f)
unitVector = TGPaintMultiplyPoint(vector, 1.0f / distance); unitVector = TGPaintMultiplyPoint(vector, 1.0f / distance);
@ -168,27 +169,29 @@ typedef struct
for (f = state.remainder; f <= distance; f += step, pressure += pressureStep) for (f = state.remainder; f <= distance; f += step, pressure += pressureStep)
{ {
CGFloat alpha = boldenFirst ? boldenedAlpha : state.alpha; CGFloat alpha = boldenFirst ? boldenedAlpha : state.alpha;
CGFloat brushSize = brushWeight; CGFloat brushSize = MAX(1.0, brushWeight - state.brushDynamic * pressure * brushWeight);
// CGFloat brushSize = MIN(brushWeight, brushWeight - pressure * brushWeight * 0.55f); // CGFloat brushSize = brushWeight;
[state addPoint:start size:brushSize angle:vectorAngle alpha:alpha index:i]; [state addPoint:start size:brushSize angle:vectorAngle alpha:alpha index:i];
start = TGPaintAddPoints(start, TGPaintMultiplyPoint(unitVector, step)); start = TGPaintAddPoints(start, TGPaintMultiplyPoint(unitVector, step));
i++;
boldenFirst = false; boldenFirst = false;
pressureStep = pDelta / (distance / step); pChange += pressureStep;
i++;
} }
// NSLog(@"final pressure %f", pressure);
if (boldenLast) if (boldenLast)
{ {
[state appendValuesCount:1]; [state appendValuesCount:1];
CGFloat brushSize = MIN(brushWeight, brushWeight - pressure * brushWeight * 0.65f); CGFloat brushSize = MAX(1.0, brushWeight - state.brushDynamic * pressure * brushWeight);
[state addPoint:location.CGPoint size:brushSize angle:vectorAngle alpha:boldenedAlpha index:i]; [state addPoint:location.CGPoint size:brushSize angle:vectorAngle alpha:boldenedAlpha index:i];
} }
state.remainder = f - distance; state.remainder = f - distance;
state.pressureRemainder = pChange - pDelta;
} }
+ (CGRect)_drawWithState:(TGPaintRenderState *)state + (CGRect)_drawWithState:(TGPaintRenderState *)state
@ -292,6 +295,7 @@ typedef struct
+ (CGRect)renderPath:(TGPaintPath *)path renderState:(TGPaintRenderState *)renderState + (CGRect)renderPath:(TGPaintPath *)path renderState:(TGPaintRenderState *)renderState
{ {
renderState.brushWeight = path.baseWeight; renderState.brushWeight = path.baseWeight;
renderState.brushDynamic = path.brush.dynamic;
renderState.spacing = path.brush.spacing; renderState.spacing = path.brush.spacing;
renderState.alpha = path.brush.alpha; renderState.alpha = path.brush.alpha;
renderState.angle = path.brush.angle; renderState.angle = path.brush.angle;
@ -313,6 +317,7 @@ typedef struct
} }
path.remainder = renderState.remainder; path.remainder = renderState.remainder;
path.pressureRemainder = renderState.pressureRemainder;
return [self _drawWithState:renderState]; return [self _drawWithState:renderState];
} }

View File

@ -4,7 +4,7 @@
@interface TGPaintSwatch : NSObject @interface TGPaintSwatch : NSObject
@property (nonatomic, readonly) UIColor *color; @property (nonatomic, readonly) UIColor *color;
@property (nonatomic, readonly) CGFloat colorLocaton; @property (nonatomic, readonly) CGFloat colorLocation;
@property (nonatomic, readonly) CGFloat brushWeight; @property (nonatomic, readonly) CGFloat brushWeight;
+ (instancetype)swatchWithColor:(UIColor *)color colorLocation:(CGFloat)colorLocation brushWeight:(CGFloat)brushWeight; + (instancetype)swatchWithColor:(UIColor *)color colorLocation:(CGFloat)colorLocation brushWeight:(CGFloat)brushWeight;

View File

@ -11,14 +11,14 @@
return false; return false;
TGPaintSwatch *swatch = (TGPaintSwatch *)object; TGPaintSwatch *swatch = (TGPaintSwatch *)object;
return [swatch.color isEqual:self.color] && fabs(swatch.colorLocaton - self.colorLocaton) < FLT_EPSILON && fabs(swatch.brushWeight - self.brushWeight) < FLT_EPSILON; return [swatch.color isEqual:self.color] && fabs(swatch.colorLocation - self.colorLocation) < FLT_EPSILON && fabs(swatch.brushWeight - self.brushWeight) < FLT_EPSILON;
} }
+ (instancetype)swatchWithColor:(UIColor *)color colorLocation:(CGFloat)colorLocation brushWeight:(CGFloat)brushWeight + (instancetype)swatchWithColor:(UIColor *)color colorLocation:(CGFloat)colorLocation brushWeight:(CGFloat)brushWeight
{ {
TGPaintSwatch *swatch = [[TGPaintSwatch alloc] init]; TGPaintSwatch *swatch = [[TGPaintSwatch alloc] init];
swatch->_color = color; swatch->_color = color;
swatch->_colorLocaton = colorLocation; swatch->_colorLocation = colorLocation;
swatch->_brushWeight = brushWeight; swatch->_brushWeight = brushWeight;
return swatch; return swatch;

View File

@ -10,19 +10,20 @@
#import "TGPaintBrush.h" #import "TGPaintBrush.h"
#import "TGPaintBrushPreview.h" #import "TGPaintBrushPreview.h"
const CGFloat TGPhotoBrushSettingsViewMargin = 19.0f; const CGFloat TGPhotoBrushSettingsViewMargin = 10.0f;
const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f; const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
@interface TGPhotoBrushSettingsView () @interface TGPhotoBrushSettingsView ()
{ {
NSArray *_brushes; NSArray *_brushes;
TGPaintBrushPreview *_preview;
UIImageView *_backgroundView; UIView *_wrapperView;
UIView *_contentView;
UIVisualEffectView *_effectView;
NSArray *_brushViews; NSArray *_brushViews;
NSArray *_brushIconViews;
NSArray *_brushSeparatorViews; NSArray *_brushSeparatorViews;
UIImageView *_selectedCheckView;
UIImage *_landscapeLeftBackgroundImage; UIImage *_landscapeLeftBackgroundImage;
UIImage *_landscapeRightBackgroundImage; UIImage *_landscapeRightBackgroundImage;
@ -40,80 +41,110 @@ const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
if (self != nil) if (self != nil)
{ {
_brushes = brushes; _brushes = brushes;
_preview = preview;
_interfaceOrientation = UIInterfaceOrientationPortrait; _interfaceOrientation = UIInterfaceOrientationPortrait;
_backgroundView = [[UIImageView alloc] init]; _wrapperView = [[UIView alloc] init];
//_backgroundView.alpha = 0.98f; _wrapperView.clipsToBounds = true;
[self addSubview:_backgroundView]; _wrapperView.layer.cornerRadius = 12.0;
[self addSubview:_wrapperView];
_effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
_effectView.alpha = 0.0f;
_effectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_wrapperView addSubview:_effectView];
_contentView = [[UIView alloc] init];
_contentView.alpha = 0.0f;
_contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_wrapperView addSubview:_contentView];
UIFont *font = [UIFont systemFontOfSize:17];
NSMutableArray *brushViews = [[NSMutableArray alloc] init]; NSMutableArray *brushViews = [[NSMutableArray alloc] init];
NSMutableArray *brushIconViews = [[NSMutableArray alloc] init];
NSMutableArray *separatorViews = [[NSMutableArray alloc] init]; NSMutableArray *separatorViews = [[NSMutableArray alloc] init];
[brushes enumerateObjectsUsingBlock:^(__unused TGPaintBrush *brush, NSUInteger index, __unused BOOL *stop) [brushes enumerateObjectsUsingBlock:^(__unused TGPaintBrush *brush, NSUInteger index, __unused BOOL *stop)
{ {
TGModernButton *button = [[TGModernButton alloc] initWithFrame:CGRectMake(0, TGPhotoBrushSettingsViewMargin + index * TGPhotoBrushSettingsItemHeight, 0, 0)]; NSString *title;
button.tag = index; UIImage *icon;
button.imageView.contentMode = UIViewContentModeCenter; switch (index) {
button.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 30.0f, 0.0f, 0.0f); case 0:
[button addTarget:self action:@selector(brushButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; title = TGLocalized(@"Paint.Pen");
[self addSubview:button]; icon = [UIImage imageNamed:@"Editor/BrushPen"];
break;
case 1:
title = TGLocalized(@"Paint.Marker");
icon = [UIImage imageNamed:@"Editor/BrushMarker"];
break;
case 2:
title = TGLocalized(@"Paint.Neon");
icon = [UIImage imageNamed:@"Editor/BrushNeon"];
break;
case 3:
title = TGLocalized(@"Paint.Arrow");
icon = [UIImage imageNamed:@"Editor/BrushArrow"];
break;
default:
break;
}
TGModernButton *button = [[TGModernButton alloc] initWithFrame:CGRectMake(0, index * TGPhotoBrushSettingsItemHeight, 0, 0)];
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
button.titleLabel.font = font;
button.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 16.0f, 0.0f, 0.0f);
button.tag = index;
[button setTitle:title forState:UIControlStateNormal];
[button setTitleColor:[UIColor whiteColor]];
[button addTarget:self action:@selector(brushButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[_contentView addSubview:button];
[brushViews addObject:button]; [brushViews addObject:button];
UIImageView *iconView = [[UIImageView alloc] initWithImage:TGTintedImage(icon, [UIColor whiteColor])];
[button addSubview:iconView];
[brushIconViews addObject:iconView];
if (index != brushes.count - 1) if (index != brushes.count - 1)
{ {
UIView *separatorView = [[UIView alloc] init]; UIView *separatorView = [[UIView alloc] init];
separatorView.backgroundColor = UIColorRGB(0xd6d6da); separatorView.backgroundColor = UIColorRGBA(0xffffff, 0.2);
[self addSubview:separatorView]; [_contentView addSubview:separatorView];
[separatorViews addObject:separatorView]; [separatorViews addObject:separatorView];
} }
}]; }];
_brushViews = brushViews; _brushViews = brushViews;
_brushIconViews = brushIconViews;
_brushSeparatorViews = separatorViews; _brushSeparatorViews = separatorViews;
_selectedCheckView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PaintCheck")];
_selectedCheckView.frame = CGRectMake(15.0f, 16.0f, _selectedCheckView.frame.size.width, _selectedCheckView.frame.size.height);
} }
return self; return self;
} }
- (void)brushButtonPressed:(TGModernButton *)sender - (void)brushButtonPressed:(TGModernButton *)sender
{ {
[sender addSubview:_selectedCheckView];
if (self.brushChanged != nil) if (self.brushChanged != nil)
self.brushChanged(_brushes[sender.tag]); self.brushChanged(_brushes[sender.tag]);
} }
- (void)present - (void)present
{ {
self.alpha = 0.0f; [UIView animateWithDuration:0.25 animations:^
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[self _setupBrushPreviews];
[UIView animateWithDuration:0.2 animations:^
{ {
self.alpha = 1.0f; _effectView.alpha = 1.0f;
_contentView.alpha = 1.0f;
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
//self.layer.shouldRasterize = false;
}]; }];
} }
- (void)dismissWithCompletion:(void (^)(void))completion - (void)dismissWithCompletion:(void (^)(void))completion
{ {
self.layer.rasterizationScale = TGScreenScaling(); [UIView animateWithDuration:0.2 animations:^
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.15 animations:^
{ {
self.alpha = 0.0f; _effectView.alpha = 0.0f;
_contentView.alpha = 0.0f;
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
if (completion != nil) if (completion != nil)
@ -121,90 +152,38 @@ const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
}]; }];
} }
- (void)_setupBrushPreviews
{
[_brushes enumerateObjectsUsingBlock:^(TGPaintBrush *aBrush, NSUInteger index, __unused BOOL *stop)
{
UIImage *image = aBrush.previewImage;
if (image == nil)
{
image = [_preview imageForBrush:aBrush size:CGSizeMake([self sizeThatFits:CGSizeZero].width - 85.0f, TGPhotoBrushSettingsItemHeight)];
aBrush.previewImage = image;
}
[_brushViews[index] setImage:image forState:UIControlStateNormal];
}];
}
- (TGPaintBrush *)brush
{
return _brushes[_selectedCheckView.superview.tag];
}
- (void)setBrush:(TGPaintBrush *)brush
{
[_brushes enumerateObjectsUsingBlock:^(TGPaintBrush *aBrush, NSUInteger index, BOOL *stop)
{
if ([brush isEqual:aBrush])
{
[_brushViews[index] addSubview:_selectedCheckView];
*stop = true;
}
}];
}
- (CGSize)sizeThatFits:(CGSize)__unused size - (CGSize)sizeThatFits:(CGSize)__unused size
{ {
return CGSizeMake(256, _brushViews.count * TGPhotoBrushSettingsItemHeight + TGPhotoBrushSettingsViewMargin * 2); return CGSizeMake(220, _brushViews.count * TGPhotoBrushSettingsItemHeight + TGPhotoBrushSettingsViewMargin * 2);
} }
- (void)setInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation - (void)setInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{ {
_interfaceOrientation = interfaceOrientation; _interfaceOrientation = interfaceOrientation;
switch (self.interfaceOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
_backgroundView.image = [TGPhotoPaintSettingsView landscapeLeftBackgroundImage];
}
break;
case UIInterfaceOrientationLandscapeRight:
{
_backgroundView.image = [TGPhotoPaintSettingsView landscapeRightBackgroundImage];
}
break;
default:
{
_backgroundView.image = [TGPhotoPaintSettingsView portraitBackgroundImage];
}
break;
}
[self setNeedsLayout]; [self setNeedsLayout];
} }
- (void)layoutSubviews - (void)layoutSubviews
{ {
CGFloat arrowSize = 0.0f;
switch (self.interfaceOrientation) switch (self.interfaceOrientation)
{ {
case UIInterfaceOrientationLandscapeLeft: case UIInterfaceOrientationLandscapeLeft:
{ {
_backgroundView.frame = CGRectMake(TGPhotoBrushSettingsViewMargin - 13.0f, TGPhotoBrushSettingsViewMargin, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2 + 13.0f, self.frame.size.height - TGPhotoBrushSettingsViewMargin * 2); _wrapperView.frame = CGRectMake(TGPhotoBrushSettingsViewMargin - arrowSize, TGPhotoBrushSettingsViewMargin, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2 + arrowSize, self.frame.size.height - TGPhotoBrushSettingsViewMargin * 2);
} }
break; break;
case UIInterfaceOrientationLandscapeRight: case UIInterfaceOrientationLandscapeRight:
{ {
_backgroundView.frame = CGRectMake(TGPhotoBrushSettingsViewMargin, TGPhotoBrushSettingsViewMargin, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2 + 13.0f, self.frame.size.height - TGPhotoBrushSettingsViewMargin * 2); _wrapperView.frame = CGRectMake(TGPhotoBrushSettingsViewMargin, TGPhotoBrushSettingsViewMargin, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2 + arrowSize, self.frame.size.height - TGPhotoBrushSettingsViewMargin * 2);
} }
break; break;
default: default:
{ {
_backgroundView.frame = CGRectMake(TGPhotoBrushSettingsViewMargin, TGPhotoBrushSettingsViewMargin, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2, self.frame.size.height - TGPhotoBrushSettingsViewMargin * 2 + 13.0f); _wrapperView.frame = CGRectMake(TGPhotoBrushSettingsViewMargin, TGPhotoBrushSettingsViewMargin, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2, self.frame.size.height - TGPhotoBrushSettingsViewMargin * 2 + arrowSize);
} }
break; break;
} }
@ -213,13 +192,17 @@ const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
[_brushViews enumerateObjectsUsingBlock:^(TGModernButton *view, NSUInteger index, __unused BOOL *stop) [_brushViews enumerateObjectsUsingBlock:^(TGModernButton *view, NSUInteger index, __unused BOOL *stop)
{ {
view.frame = CGRectMake(TGPhotoBrushSettingsViewMargin, TGPhotoBrushSettingsViewMargin + TGPhotoBrushSettingsItemHeight * index, self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2, TGPhotoBrushSettingsItemHeight); view.frame = CGRectMake(0.0f, TGPhotoBrushSettingsItemHeight * index, _contentView.frame.size.width, TGPhotoBrushSettingsItemHeight);
}];
[_brushIconViews enumerateObjectsUsingBlock:^(UIImageView *view, NSUInteger index, __unused BOOL *stop)
{
view.frame = CGRectMake(_contentView.frame.size.width - 42.0f, (TGPhotoBrushSettingsItemHeight - view.frame.size.height) / 2.0, view.frame.size.width, view.frame.size.height);
}]; }];
[_brushSeparatorViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger index, __unused BOOL *stop) [_brushSeparatorViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger index, __unused BOOL *stop)
{ {
view.frame = CGRectMake(TGPhotoBrushSettingsViewMargin + 44.0f, TGPhotoBrushSettingsViewMargin + TGPhotoBrushSettingsItemHeight * (index + 1), self.frame.size.width - TGPhotoBrushSettingsViewMargin * 2 - 44.0f, thickness); view.frame = CGRectMake(0.0f, TGPhotoBrushSettingsItemHeight * (index + 1), _contentView.frame.size.width, thickness);
}]; }];
} }

View File

@ -1552,7 +1552,7 @@
if ([item isKindOfClass:[TGMediaAsset class]]) if ([item isKindOfClass:[TGMediaAsset class]])
assetSignal = [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)item]; assetSignal = [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)item];
else if ([item isKindOfClass:[TGCameraCapturedVideo class]]) else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
assetSignal = [SSignal single:((TGCameraCapturedVideo *)item).avAsset]; assetSignal = ((TGCameraCapturedVideo *)item).avAsset;
[assetSignal startWithNext:^(AVAsset *asset) [assetSignal startWithNext:^(AVAsset *asset)
{ {

View File

@ -15,6 +15,8 @@
- (void)updateVisibility:(bool)visible; - (void)updateVisibility:(bool)visible;
- (UIColor *)colorAtPoint:(CGPoint)point;
- (void)setupWithPaintingData:(TGPaintingData *)paintingData; - (void)setupWithPaintingData:(TGPaintingData *)paintingData;
- (TGPhotoPaintEntityView *)createEntityViewWithEntity:(TGPhotoPaintEntity *)entity; - (TGPhotoPaintEntityView *)createEntityViewWithEntity:(TGPhotoPaintEntity *)entity;

View File

@ -47,7 +47,7 @@
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer - (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer
{ {
CGPoint location = [gestureRecognizer locationInView:self]; CGPoint point = [gestureRecognizer locationInView:self];
NSMutableArray *intersectedViews = [[NSMutableArray alloc] init]; NSMutableArray *intersectedViews = [[NSMutableArray alloc] init];
for (TGPhotoPaintEntityView *view in self.subviews) for (TGPhotoPaintEntityView *view in self.subviews)
@ -55,7 +55,7 @@
if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) if (![view isKindOfClass:[TGPhotoPaintEntityView class]])
continue; continue;
if ([view pointInside:[view convertPoint:location fromView:self] withEvent:nil]) if ([view pointInside:[view convertPoint:point fromView:self] withEvent:nil])
[intersectedViews addObject:view]; [intersectedViews addObject:view];
} }
@ -65,7 +65,7 @@
__block TGPhotoPaintEntityView *subresult = nil; __block TGPhotoPaintEntityView *subresult = nil;
[intersectedViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TGPhotoPaintEntityView *view, __unused NSUInteger index, BOOL *stop) [intersectedViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TGPhotoPaintEntityView *view, __unused NSUInteger index, BOOL *stop)
{ {
if ([view precisePointInside:[view convertPoint:location fromView:self]]) if ([view precisePointInside:[view convertPoint:point fromView:self]])
{ {
subresult = view; subresult = view;
*stop = true; *stop = true;
@ -83,6 +83,40 @@
self.entitySelected(result); self.entitySelected(result);
} }
- (UIColor *)colorAtPoint:(CGPoint)point {
NSMutableArray *intersectedViews = [[NSMutableArray alloc] init];
for (TGPhotoPaintEntityView *view in self.subviews)
{
if (![view isKindOfClass:[TGPhotoPaintEntityView class]])
continue;
if ([view pointInside:[view convertPoint:point fromView:self] withEvent:nil])
[intersectedViews addObject:view];
}
TGPhotoPaintEntityView *result = nil;
if (intersectedViews.count > 1)
{
__block TGPhotoPaintEntityView *subresult = nil;
[intersectedViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TGPhotoPaintEntityView *view, __unused NSUInteger index, BOOL *stop)
{
if ([view precisePointInside:[view convertPoint:point fromView:self]])
{
subresult = view;
*stop = true;
}
}];
result = subresult ?: intersectedViews.lastObject;
}
else if (intersectedViews.count == 1)
{
result = intersectedViews.firstObject;
}
return [result colorAtPoint:[result convertPoint:point fromView:self]];
}
- (NSUInteger)entitiesCount - (NSUInteger)entitiesCount
{ {
return MAX(0, (NSInteger)self.subviews.count - 1); return MAX(0, (NSInteger)self.subviews.count - 1);

View File

@ -137,7 +137,7 @@ const CGFloat TGPhotoPaintDefaultColorLocation = 1.0f;
- (void)setSwatch:(TGPaintSwatch *)swatch - (void)setSwatch:(TGPaintSwatch *)swatch
{ {
[self setLocation:swatch.colorLocaton]; [self setLocation:swatch.colorLocation];
[self setWeight:swatch.brushWeight]; [self setWeight:swatch.brushWeight];
} }

View File

@ -23,6 +23,7 @@
#import "TGPaintRadialBrush.h" #import "TGPaintRadialBrush.h"
#import "TGPaintEllipticalBrush.h" #import "TGPaintEllipticalBrush.h"
#import "TGPaintNeonBrush.h" #import "TGPaintNeonBrush.h"
#import "TGPaintArrowBrush.h"
#import "TGPaintCanvas.h" #import "TGPaintCanvas.h"
#import "TGPaintingWrapperView.h" #import "TGPaintingWrapperView.h"
#import "TGPaintState.h" #import "TGPaintState.h"
@ -45,6 +46,7 @@
#import "TGPhotoEntitiesContainerView.h" #import "TGPhotoEntitiesContainerView.h"
#import "TGPhotoStickerEntityView.h" #import "TGPhotoStickerEntityView.h"
#import "TGPhotoTextEntityView.h" #import "TGPhotoTextEntityView.h"
#import "TGPhotoPaintEyedropperView.h"
#import "TGPaintFaceDetector.h" #import "TGPaintFaceDetector.h"
#import "TGPhotoMaskPosition.h" #import "TGPhotoMaskPosition.h"
@ -83,6 +85,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
UIView *_contentWrapperView; UIView *_contentWrapperView;
UIView *_dimView; UIView *_dimView;
TGModernButton *_doneButton;
TGPhotoPaintActionsView *_landscapeActionsView; TGPhotoPaintActionsView *_landscapeActionsView;
TGPhotoPaintActionsView *_portraitActionsView; TGPhotoPaintActionsView *_portraitActionsView;
@ -104,6 +107,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
TGPhotoPaintSelectionContainerView *_selectionContainerView; TGPhotoPaintSelectionContainerView *_selectionContainerView;
TGPhotoPaintEntitySelectionView *_entitySelectionView; TGPhotoPaintEntitySelectionView *_entitySelectionView;
TGPhotoPaintEyedropperView *_eyedropperView;
TGPhotoTextEntityView *_editedTextView; TGPhotoTextEntityView *_editedTextView;
CGPoint _editedTextCenter; CGPoint _editedTextCenter;
@ -122,6 +126,11 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
bool _enableStickers; bool _enableStickers;
NSData *_eyedropperBackgroundData;
CGSize _eyedropperBackgroundSize;
NSInteger _eyedropperBackgroundBytesPerRow;
CGBitmapInfo _eyedropperBackgroundInfo;
id<LegacyComponentsContext> _context; id<LegacyComponentsContext> _context;
} }
@ -151,10 +160,11 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[ [
[[TGPaintRadialBrush alloc] init], [[TGPaintRadialBrush alloc] init],
[[TGPaintEllipticalBrush alloc] init], [[TGPaintEllipticalBrush alloc] init],
[[TGPaintNeonBrush alloc] init] [[TGPaintNeonBrush alloc] init],
[[TGPaintArrowBrush alloc] init],
]; ];
_selectedTextFont = [[TGPhotoPaintFont availableFonts] firstObject]; _selectedTextFont = [[TGPhotoPaintFont availableFonts] firstObject];
_selectedTextStyle = TGPhotoPaintTextEntityStyleBorder; _selectedTextStyle = TGPhotoPaintTextEntityStyleFramed;
if (_photoEditor.paintingData.undoManager != nil) if (_photoEditor.paintingData.undoManager != nil)
_undoManager = [_photoEditor.paintingData.undoManager copy]; _undoManager = [_photoEditor.paintingData.undoManager copy];
@ -270,6 +280,25 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_selectionContainerView.clipsToBounds = false; _selectionContainerView.clipsToBounds = false;
[_containerView addSubview:_selectionContainerView]; [_containerView addSubview:_selectionContainerView];
_eyedropperView = [[TGPhotoPaintEyedropperView alloc] init];
_eyedropperView.locationChanged = ^(CGPoint location, bool finished) {
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf != nil)
{
UIColor *color = [strongSelf colorAtPoint:location];
strongSelf->_eyedropperView.color = color;
if (finished) {
TGPaintSwatch *swatch = [TGPaintSwatch swatchWithColor:color colorLocation:0.5 brushWeight:strongSelf->_portraitSettingsView.swatch.brushWeight];
[strongSelf setCurrentSwatch:swatch sender:nil];
[strongSelf commitEyedropper:false];
}
}
};
_eyedropperView.hidden = true;
[_selectionContainerView addSubview:_eyedropperView];
_wrapperView = [[TGPhotoPaintSparseView alloc] initWithFrame:CGRectZero]; _wrapperView = [[TGPhotoPaintSparseView alloc] initWithFrame:CGRectZero];
[self.view addSubview:_wrapperView]; [self.view addSubview:_wrapperView];
@ -307,12 +336,22 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_landscapeActionsView.clearPressed = clearPressed; _landscapeActionsView.clearPressed = clearPressed;
[_wrapperView addSubview:_landscapeActionsView]; [_wrapperView addSubview:_landscapeActionsView];
_doneButton = [[TGModernButton alloc] init];
_doneButton.alpha = 0.0f;
_doneButton.userInteractionEnabled = false;
[_doneButton setTitle:TGLocalized(@"Common.Done") forState:UIControlStateNormal];
_doneButton.titleLabel.font = TGSystemFontOfSize(17.0);
[_doneButton sizeToFit];
[_wrapperView addSubview:_doneButton];
void (^settingsPressed)(void) = ^ void (^settingsPressed)(void) = ^
{ {
__strong TGPhotoPaintController *strongSelf = weakSelf; __strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil) if (strongSelf == nil)
return; return;
[strongSelf commitEyedropper:true];
if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
[strongSelf presentTextSettingsView]; [strongSelf presentTextSettingsView];
else if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) else if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]])
@ -321,12 +360,23 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[strongSelf presentBrushSettingsView]; [strongSelf presentBrushSettingsView];
}; };
void (^eyedropperPressed)(void) = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[self enableEyedropper];
};
void (^beganColorPicking)(void) = ^ void (^beganColorPicking)(void) = ^
{ {
__strong TGPhotoPaintController *strongSelf = weakSelf; __strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil) if (strongSelf == nil)
return; return;
[strongSelf commitEyedropper:true];
if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
[strongSelf setDimHidden:false animated:true]; [strongSelf setDimHidden:false animated:true];
}; };
@ -346,6 +396,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (strongSelf == nil) if (strongSelf == nil)
return; return;
[strongSelf commitEyedropper:true];
[strongSelf setCurrentSwatch:swatch sender:sender]; [strongSelf setCurrentSwatch:swatch sender:sender];
if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
@ -353,6 +405,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
}; };
_portraitSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context]; _portraitSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context];
_portraitSettingsView.eyedropperPressed = eyedropperPressed;
_portraitSettingsView.beganColorPicking = beganColorPicking; _portraitSettingsView.beganColorPicking = beganColorPicking;
_portraitSettingsView.changedColor = changedColor; _portraitSettingsView.changedColor = changedColor;
_portraitSettingsView.finishedColorPicking = finishedColorPicking; _portraitSettingsView.finishedColorPicking = finishedColorPicking;
@ -362,6 +415,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[_portraitToolsWrapperView addSubview:_portraitSettingsView]; [_portraitToolsWrapperView addSubview:_portraitSettingsView];
_landscapeSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context]; _landscapeSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context];
_landscapeSettingsView.eyedropperPressed = eyedropperPressed;
_landscapeSettingsView.beganColorPicking = beganColorPicking; _landscapeSettingsView.beganColorPicking = beganColorPicking;
_landscapeSettingsView.changedColor = changedColor; _landscapeSettingsView.changedColor = changedColor;
_landscapeSettingsView.finishedColorPicking = finishedColorPicking; _landscapeSettingsView.finishedColorPicking = finishedColorPicking;
@ -489,7 +543,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (TGPhotoEditorTab)availableTabs - (TGPhotoEditorTab)availableTabs
{ {
TGPhotoEditorTab result = TGPhotoEditorPaintTab | TGPhotoEditorEraserTab | TGPhotoEditorTextTab; TGPhotoEditorTab result = TGPhotoEditorPaintTab | TGPhotoEditorEraserTab | TGPhotoEditorTextTab;
if (_enableStickers) { if (_enableStickers && _stickersContext != nil) {
result |= TGPhotoEditorStickerTab; result |= TGPhotoEditorStickerTab;
} }
return result; return result;
@ -497,6 +551,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (void)handleTabAction:(TGPhotoEditorTab)tab - (void)handleTabAction:(TGPhotoEditorTab)tab
{ {
[self commitEyedropper:true];
switch (tab) switch (tab)
{ {
case TGPhotoEditorStickerTab: case TGPhotoEditorStickerTab:
@ -625,6 +681,49 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
#pragma mark - Data Handling #pragma mark - Data Handling
- (UIImage *)eyedropperImage
{
UIImage *backgroundImage = [self.photoEditor currentResultImage];
CGSize fittedSize = TGFitSize(_painting.size, TGPhotoEditorResultImageMaxSize);
UIImage *paintingImage = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:NULL];
NSMutableArray *entities = [[NSMutableArray alloc] init];
UIImage *entitiesImage = nil;
if (paintingImage == nil && _entitiesContainerView.entitiesCount < 1)
{
return backgroundImage;
}
else if (_entitiesContainerView.entitiesCount > 0)
{
for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews)
{
if (![view isKindOfClass:[TGPhotoPaintEntityView class]])
continue;
TGPhotoPaintEntity *entity = [view entity];
if (entity != nil) {
[entities addObject:entity];
}
}
entitiesImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:nil still:true];
}
if (entitiesImage == nil && paintingImage == nil) {
return backgroundImage;
} else {
UIGraphicsBeginImageContextWithOptions(fittedSize, false, 1.0);
[backgroundImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)];
[paintingImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)];
[entitiesImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
}
- (TGPaintingData *)_prepareResultData - (TGPaintingData *)_prepareResultData
{ {
if (_resultData != nil) if (_resultData != nil)
@ -698,6 +797,60 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
return [self _prepareResultData]; return [self _prepareResultData];
} }
- (void)enableEyedropper {
if (!_eyedropperView.isHidden)
return;
[self selectEntityView:nil];
self.controlVideoPlayback(false);
[_entitiesContainerView updateVisibility:false];
UIImage *image = [self eyedropperImage];
CGImageRef cgImage = image.CGImage;
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
_eyedropperBackgroundData = (__bridge NSData *)pixelData;
_eyedropperBackgroundSize = image.size;
_eyedropperBackgroundBytesPerRow = CGImageGetBytesPerRow(cgImage);
_eyedropperBackgroundInfo = CGImageGetBitmapInfo(cgImage);
[_eyedropperView update];
[_eyedropperView present];
}
- (void)commitEyedropper:(bool)immediate {
self.controlVideoPlayback(true);
[_entitiesContainerView updateVisibility:true];
_eyedropperBackgroundData = nil;
_eyedropperBackgroundSize = CGSizeZero;
_eyedropperBackgroundBytesPerRow = 0;
_eyedropperBackgroundInfo = 0;
double timeout = immediate ? 0.0 : 0.2;
TGDispatchAfter(timeout, dispatch_get_main_queue(), ^{
[_eyedropperView dismiss];
});
}
- (UIColor *)colorFromData:(NSData *)data width:(NSInteger)width height:(NSInteger)height x:(NSInteger)x y:(NSInteger)y bpr:(NSInteger)bpr {
uint8_t *pixel = (uint8_t *)data.bytes + bpr * y + x * 4;
if (_eyedropperBackgroundInfo & kCGBitmapByteOrder32Little) {
return [UIColor colorWithRed:pixel[2] / 255.0 green:pixel[1] / 255.0 blue:pixel[0] / 255.0 alpha:1.0];
} else {
return [UIColor colorWithRed:pixel[0] / 255.0 green:pixel[1] / 255.0 blue:pixel[2] / 255.0 alpha:1.0];
}
}
- (UIColor *)colorAtPoint:(CGPoint)point
{
CGPoint convertedPoint = CGPointMake(point.x / _eyedropperView.bounds.size.width * _eyedropperBackgroundSize.width, point.y / _eyedropperView.bounds.size.height * _eyedropperBackgroundSize.height);
UIColor *backgroundColor = [self colorFromData:_eyedropperBackgroundData width:_eyedropperBackgroundSize.width height:_eyedropperBackgroundSize.height x:convertedPoint.x y:convertedPoint.y bpr:_eyedropperBackgroundBytesPerRow];
return backgroundColor;
}
#pragma mark - Entities #pragma mark - Entities
- (void)selectEntityView:(TGPhotoPaintEntityView *)view - (void)selectEntityView:(TGPhotoPaintEntityView *)view
@ -734,7 +887,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if ([view isKindOfClass:[TGPhotoTextEntityView class]]) if ([view isKindOfClass:[TGPhotoTextEntityView class]])
{ {
TGPaintSwatch *textSwatch = ((TGPhotoPaintTextEntity *)view.entity).swatch; TGPaintSwatch *textSwatch = ((TGPhotoPaintTextEntity *)view.entity).swatch;
[self setCurrentSwatch:[TGPaintSwatch swatchWithColor:textSwatch.color colorLocation:textSwatch.colorLocaton brushWeight:_portraitSettingsView.swatch.brushWeight] sender:nil]; [self setCurrentSwatch:[TGPaintSwatch swatchWithColor:textSwatch.color colorLocation:textSwatch.colorLocation brushWeight:_portraitSettingsView.swatch.brushWeight] sender:nil];
} }
_entitySelectionView = [view createSelectionView]; _entitySelectionView = [view createSelectionView];
@ -1061,7 +1214,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch; TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch;
TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:[UIColor whiteColor] colorLocation:1.0f brushWeight:currentSwatch.brushWeight]; TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:[UIColor whiteColor] colorLocation:1.0f brushWeight:currentSwatch.brushWeight];
TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:[UIColor blackColor] colorLocation:0.85f brushWeight:currentSwatch.brushWeight]; TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:[UIColor blackColor] colorLocation:0.85f brushWeight:currentSwatch.brushWeight];
[self setCurrentSwatch:_selectedTextStyle == TGPhotoPaintTextEntityStyleBorder ? blackSwatch : whiteSwatch sender:nil]; [self setCurrentSwatch:_selectedTextStyle == TGPhotoPaintTextEntityStyleOutlined ? blackSwatch : whiteSwatch sender:nil];
CGFloat maxWidth = [self fittedContentSize].width - 26.0f; CGFloat maxWidth = [self fittedContentSize].width - 26.0f;
TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:@"" font:_selectedTextFont swatch:_portraitSettingsView.swatch baseFontSize:[self _textBaseFontSizeForCurrentPainting] maxWidth:maxWidth style:_selectedTextStyle]; TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:@"" font:_selectedTextFont swatch:_portraitSettingsView.swatch baseFontSize:[self _textBaseFontSizeForCurrentPainting] maxWidth:maxWidth style:_selectedTextStyle];
@ -1258,13 +1411,35 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (void)updateSettingsButton - (void)updateSettingsButton
{ {
if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) {
[self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconText]; TGPhotoPaintSettingsViewIcon icon;
else if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) switch (((TGPhotoTextEntityView *)_currentEntityView).entity.style) {
case TGPhotoPaintTextEntityStyleRegular:
icon = TGPhotoPaintSettingsViewIconTextRegular;
break;
case TGPhotoPaintTextEntityStyleOutlined:
icon = TGPhotoPaintSettingsViewIconTextOutlined;
break;
case TGPhotoPaintTextEntityStyleFramed:
icon = TGPhotoPaintSettingsViewIconTextFramed;
break;
}
[self setSettingsButtonIcon:icon];
}
else if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) {
[self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconMirror]; [self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconMirror];
else }
[self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconBrush]; else {
TGPhotoPaintSettingsViewIcon icon = TGPhotoPaintSettingsViewIconBrushPen;
if ([_canvasView.state.brush isKindOfClass:[TGPaintEllipticalBrush class]]) {
icon = TGPhotoPaintSettingsViewIconBrushMarker;
} else if ([_canvasView.state.brush isKindOfClass:[TGPaintNeonBrush class]]) {
icon = TGPhotoPaintSettingsViewIconBrushNeon;
} else if ([_canvasView.state.brush isKindOfClass:[TGPaintArrowBrush class]]) {
icon = TGPhotoPaintSettingsViewIconBrushArrow;
}
[self setSettingsButtonIcon:icon];
}
[self _updateTabs]; [self _updateTabs];
} }
@ -1347,12 +1522,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (strongSelf == nil) if (strongSelf == nil)
return; return;
if (strongSelf->_canvasView.state.eraser && brush.lightSaber) if (strongSelf->_canvasView.state.eraser && (brush.lightSaber || brush.arrow))
brush = strongSelf->_brushes.firstObject; brush = strongSelf->_brushes.firstObject;
[strongSelf->_canvasView setBrush:brush]; [strongSelf->_canvasView setBrush:brush];
[strongSelf settingsWrapperPressed]; [strongSelf settingsWrapperPressed];
[strongSelf updateSettingsButton];
}; };
_settingsView = view; _settingsView = view;
[view sizeToFit]; [view sizeToFit];
@ -1383,6 +1559,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[textView setFont:font]; [textView setFont:font];
[strongSelf settingsWrapperPressed]; [strongSelf settingsWrapperPressed];
[strongSelf updateSettingsButton];
}; };
view.styleChanged = ^(TGPhotoPaintTextEntityStyle style) view.styleChanged = ^(TGPhotoPaintTextEntityStyle style)
{ {
@ -1392,13 +1569,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
strongSelf->_selectedTextStyle = style; strongSelf->_selectedTextStyle = style;
if (style == TGPhotoPaintTextEntityStyleBorder && [strongSelf->_portraitSettingsView.swatch.color isEqual:[UIColor whiteColor]]) if (style == TGPhotoPaintTextEntityStyleOutlined && [strongSelf->_portraitSettingsView.swatch.color isEqual:[UIColor whiteColor]])
{ {
TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch; TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch;
TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:[UIColor blackColor] colorLocation:0.85f brushWeight:currentSwatch.brushWeight]; TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:[UIColor blackColor] colorLocation:0.85f brushWeight:currentSwatch.brushWeight];
[strongSelf setCurrentSwatch:blackSwatch sender:nil]; [strongSelf setCurrentSwatch:blackSwatch sender:nil];
} }
else if (style != TGPhotoPaintTextEntityStyleBorder && [strongSelf->_portraitSettingsView.swatch.color isEqual:UIColorRGB(0x000000)]) else if (style != TGPhotoPaintTextEntityStyleOutlined && [strongSelf->_portraitSettingsView.swatch.color isEqual:UIColorRGB(0x000000)])
{ {
TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch; TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch;
TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:[UIColor whiteColor] colorLocation:1.0f brushWeight:currentSwatch.brushWeight]; TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:[UIColor whiteColor] colorLocation:1.0f brushWeight:currentSwatch.brushWeight];
@ -1409,6 +1586,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[textView setStyle:style]; [textView setStyle:style];
[strongSelf settingsWrapperPressed]; [strongSelf settingsWrapperPressed];
[strongSelf updateSettingsButton];
}; };
_settingsView = view; _settingsView = view;
@ -1429,13 +1607,14 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (_canvasView.state.eraser) if (_canvasView.state.eraser)
{ {
if (_canvasView.state.brush.lightSaber) if (_canvasView.state.brush.lightSaber || _canvasView.state.brush.arrow)
[_canvasView setBrush:_brushes.firstObject]; [_canvasView setBrush:_brushes.firstObject];
} }
[_portraitSettingsView setHighlighted:_canvasView.state.isEraser]; [_portraitSettingsView setHighlighted:_canvasView.state.isEraser];
[_landscapeSettingsView setHighlighted:_canvasView.state.isEraser]; [_landscapeSettingsView setHighlighted:_canvasView.state.isEraser];
[self updateSettingsButton];
[self _updateTabs]; [self _updateTabs];
} }
@ -1934,17 +2113,22 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[_dimView.superview insertSubview:_dimView belowSubview:_currentEntityView]; [_dimView.superview insertSubview:_dimView belowSubview:_currentEntityView];
else else
[_dimView.superview bringSubviewToFront:_dimView]; [_dimView.superview bringSubviewToFront:_dimView];
[_doneButton.superview bringSubviewToFront:_doneButton];
} }
else else
{ {
[_entitySelectionView fadeIn]; [_entitySelectionView fadeIn];
[_dimView.superview bringSubviewToFront:_dimView]; [_dimView.superview bringSubviewToFront:_dimView];
[_doneButton.superview bringSubviewToFront:_doneButton];
} }
void (^changeBlock)(void) = ^ void (^changeBlock)(void) = ^
{ {
_dimView.alpha = hidden ? 0.0f : 1.0f; _dimView.alpha = hidden ? 0.0f : 1.0f;
_doneButton.alpha = hidden ? 0.0f : 1.0f;
}; };
if (animated) if (animated)
@ -2016,6 +2200,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_settingsViewWrapper.frame = self.parentViewController.view.bounds; _settingsViewWrapper.frame = self.parentViewController.view.bounds;
_doneButton.frame = CGRectMake(screenEdges.right - _doneButton.frame.size.width - 8.0, screenEdges.top + 2.0, _doneButton.frame.size.width, _doneButton.frame.size.height);
if (_settingsView != nil) if (_settingsView != nil)
[_settingsView setInterfaceOrientation:orientation]; [_settingsView setInterfaceOrientation:orientation];
@ -2151,6 +2337,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation); _selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation);
_selectionContainerView.frame = previewFrame; _selectionContainerView.frame = previewFrame;
_eyedropperView.frame = _selectionContainerView.bounds;
_containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height);
} }

View File

@ -221,3 +221,27 @@ const CGFloat TGPhotoPaintEntityMinScale = 0.12f;
} }
@end @end
@implementation UIView (PixelColor)
- (UIColor *)colorAtPoint:(CGPoint)point
{
if (point.x > self.bounds.size.width || point.y > self.bounds.size.height)
return nil;
unsigned char pixel[4] = {0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
CGContextTranslateCTM(context, -point.x, -point.y);
[self.layer renderInContext:context];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIColor colorWithRed:pixel[0] / 255.0 green:pixel[1] / 255.0 blue:pixel[2] / 255.0 alpha:pixel[3] / 255.0];
}
@end

View File

@ -0,0 +1,13 @@
#import <UIKit/UIKit.h>
@interface TGPhotoPaintEyedropperView : UIView
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, copy) void(^locationChanged)(CGPoint, bool);
- (void)update;
- (void)present;
- (void)dismiss;
@end

View File

@ -0,0 +1,157 @@
#import "TGPhotoPaintEyedropperView.h"
#import "TGImageUtils.h"
@interface TGPhotoPaintEyedropperIndicatorView : UIView
@property (nonatomic, strong) UIColor *color;
@end
@implementation TGPhotoPaintEyedropperIndicatorView
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
self.backgroundColor = [UIColor clearColor];
self.opaque = false;
self.userInteractionEnabled = false;
}
return self;
}
- (void)setColor:(UIColor *)color {
_color = color;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lineWidth = 1.0f + TGScreenPixel;
CGContextSetFillColorWithColor(context, _color.CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSaveGState(context);
CGContextScaleCTM(context, 0.333333, 0.333333);
CGContextSetLineWidth(context, lineWidth * 3.0);
TGDrawSvgPath(context, @"M75,0.5 C54.4273931,0.5 35.8023931,8.83869653 22.3205448,22.3205448 C8.83869653,35.8023931 0.5,54.4273931 0.5,75 C0.5,94.6543797 10.7671345,116.856807 23.8111444,136.192682 C42.4188317,163.77591 66.722394,185.676747 75,185.676747 C83.277606,185.676747 107.581168,163.77591 126.188856,136.192682 C139.232866,116.856807 149.5,94.6543797 149.5,75 C149.5,54.4273931 141.161303,35.8023931 127.679455,22.3205448 C114.197607,8.83869653 95.5726069,0.5 75,0.5 Z");
TGDrawSvgPath(context, @"M75,0.5 C54.4273931,0.5 35.8023931,8.83869653 22.3205448,22.3205448 C8.83869653,35.8023931 0.5,54.4273931 0.5,75 C0.5,94.6543797 10.7671345,116.856807 23.8111444,136.192682 C42.4188317,163.77591 66.722394,185.676747 75,185.676747 C83.277606,185.676747 107.581168,163.77591 126.188856,136.192682 C139.232866,116.856807 149.5,94.6543797 149.5,75 C149.5,54.4273931 141.161303,35.8023931 127.679455,22.3205448 C114.197607,8.83869653 95.5726069,0.5 75,0.5 S");
CGContextRestoreGState(context);
CGContextSetLineWidth(context, lineWidth);
CGContextFillEllipseInRect(context, CGRectMake(20.0, 68.0, 11.0, 11.0));
CGContextStrokeEllipseInRect(context, CGRectMake(20.0, 68.0, 11.0, 11.0));
}
@end
@interface TGPhotoPaintEyedropperView() <UIGestureRecognizerDelegate>
@end
@implementation TGPhotoPaintEyedropperView
{
TGPhotoPaintEyedropperIndicatorView *_indicatorView;
UITapGestureRecognizer *_tapGestureRecognizer;
UIPanGestureRecognizer *_panGestureRecognizer;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil) {
_indicatorView = [[TGPhotoPaintEyedropperIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 51.0, 81.0)];
_indicatorView.layer.anchorPoint = CGPointMake(0.5, 0.92);
[self addSubview:_indicatorView];
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self addGestureRecognizer:_tapGestureRecognizer];
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
_panGestureRecognizer.delegate = self;
[self addGestureRecognizer:_panGestureRecognizer];
}
return self;
}
- (void)setColor:(UIColor *)color {
_color = color;
_indicatorView.color = color;
}
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer {
CGPoint location = [gestureRecognizer locationInView:self];
[self layoutIndicator:location];
self.locationChanged(location, true);
}
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint location = [gestureRecognizer locationInView:self];
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateChanged:
{
[self layoutIndicator:location];
self.locationChanged(location, false);
}
break;
case UIGestureRecognizerStateEnded:
{
[self layoutIndicator:location];
self.locationChanged(location, true);
}
break;
default:
break;
}
}
- (void)update {
CGPoint location = CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0);
self.locationChanged(location, false);
[self layoutIndicator:location];
}
- (void)present {
self.hidden = false;
_indicatorView.alpha = 0.0f;
_indicatorView.transform = CGAffineTransformMakeScale(0.2, 0.2);
[UIView animateWithDuration:0.2 animations:^
{
_indicatorView.alpha = 1.0f;
_indicatorView.transform = CGAffineTransformIdentity;
} completion:^(__unused BOOL finished)
{
}];
}
- (void)dismiss {
if (self.hidden)
return;
[UIView animateWithDuration:0.15 animations:^
{
_indicatorView.alpha = 0.0f;
_indicatorView.transform = CGAffineTransformMakeScale(0.2, 0.2);
} completion:^(__unused BOOL finished)
{
self.hidden = true;
}];
}
- (void)layoutIndicator:(CGPoint)point {
_indicatorView.center = CGPointMake(point.x, point.y);
}
@end

View File

@ -6,8 +6,13 @@
typedef enum typedef enum
{ {
TGPhotoPaintSettingsViewIconBrush, TGPhotoPaintSettingsViewIconBrushPen,
TGPhotoPaintSettingsViewIconText, TGPhotoPaintSettingsViewIconBrushMarker,
TGPhotoPaintSettingsViewIconBrushNeon,
TGPhotoPaintSettingsViewIconBrushArrow,
TGPhotoPaintSettingsViewIconTextRegular,
TGPhotoPaintSettingsViewIconTextOutlined,
TGPhotoPaintSettingsViewIconTextFramed,
TGPhotoPaintSettingsViewIconMirror TGPhotoPaintSettingsViewIconMirror
} TGPhotoPaintSettingsViewIcon; } TGPhotoPaintSettingsViewIcon;
@ -17,7 +22,9 @@ typedef enum
@property (nonatomic, copy) void (^changedColor)(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch); @property (nonatomic, copy) void (^changedColor)(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch);
@property (nonatomic, copy) void (^finishedColorPicking)(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch); @property (nonatomic, copy) void (^finishedColorPicking)(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch);
@property (nonatomic, copy) void (^eyedropperPressed)(void);
@property (nonatomic, copy) void (^settingsPressed)(void); @property (nonatomic, copy) void (^settingsPressed)(void);
@property (nonatomic, readonly) UIButton *settingsButton; @property (nonatomic, readonly) UIButton *settingsButton;
@property (nonatomic, strong) TGPaintSwatch *swatch; @property (nonatomic, strong) TGPaintSwatch *swatch;

View File

@ -14,6 +14,7 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
@interface TGPhotoPaintSettingsView () @interface TGPhotoPaintSettingsView ()
{ {
TGPhotoPaintColorPicker *_colorPicker; TGPhotoPaintColorPicker *_colorPicker;
TGModernButton *_eyedropperButton;
TGModernButton *_settingsButton; TGModernButton *_settingsButton;
TGPhotoPaintSettingsViewIcon _icon; TGPhotoPaintSettingsViewIcon _icon;
@ -54,7 +55,13 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
}; };
[self addSubview:_colorPicker]; [self addSubview:_colorPicker];
_icon = TGPhotoPaintSettingsViewIconBrush; _icon = TGPhotoPaintSettingsViewIconBrushPen;
_eyedropperButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 44.0f, 44.0f)];
_eyedropperButton.exclusiveTouch = true;
[_eyedropperButton setImage:TGTintedImage([UIImage imageNamed:@"Editor/Eyedropper"], [UIColor whiteColor]) forState:UIControlStateNormal];
[_eyedropperButton addTarget:self action:@selector(eyedropperButtonPressed) forControlEvents:UIControlEventTouchUpInside];
// [self addSubview:_eyedropperButton];
_settingsButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 44.0f, 44.0f)]; _settingsButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 44.0f, 44.0f)];
_settingsButton.exclusiveTouch = true; _settingsButton.exclusiveTouch = true;
@ -85,6 +92,12 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
_colorPicker.orientation = interfaceOrientation; _colorPicker.orientation = interfaceOrientation;
} }
- (void)eyedropperButtonPressed
{
if (self.eyedropperPressed != nil)
self.eyedropperPressed();
}
- (void)settingsButtonPressed - (void)settingsButtonPressed
{ {
if (self.settingsPressed != nil) if (self.settingsPressed != nil)
@ -143,25 +156,35 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
- (UIImage *)_imageForIcon:(TGPhotoPaintSettingsViewIcon)icon highlighted:(bool)highlighted - (UIImage *)_imageForIcon:(TGPhotoPaintSettingsViewIcon)icon highlighted:(bool)highlighted
{ {
UIColor *color = highlighted ? [TGPhotoEditorInterfaceAssets accentColor] : [UIColor whiteColor];
UIImage *iconImage = nil; UIImage *iconImage = nil;
switch (icon) switch (icon)
{ {
case TGPhotoPaintSettingsViewIconBrush: case TGPhotoPaintSettingsViewIconBrushPen:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Brush"], [UIColor whiteColor]); iconImage = TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedPen"], color);
break; break;
case TGPhotoPaintSettingsViewIconBrushMarker:
case TGPhotoPaintSettingsViewIconText: iconImage = TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedMarker"], color);
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Font"], [UIColor whiteColor]); break;
case TGPhotoPaintSettingsViewIconBrushNeon:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedNeon"], color);
break;
case TGPhotoPaintSettingsViewIconBrushArrow:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedArrow"], color);
break;
case TGPhotoPaintSettingsViewIconTextRegular:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/TextSelectedRegular"], color);
break;
case TGPhotoPaintSettingsViewIconTextOutlined:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/TextSelectedOutlined"], color);
break;
case TGPhotoPaintSettingsViewIconTextFramed:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/TextSelectedFramed"], color);
break; break;
case TGPhotoPaintSettingsViewIconMirror: case TGPhotoPaintSettingsViewIconMirror:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Flip"], [UIColor whiteColor]); iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Flip"], color);
break; break;
} }
if (highlighted)
iconImage = TGTintedImage(iconImage, [TGPhotoEditorInterfaceAssets accentColor]);
return iconImage; return iconImage;
} }
@ -188,22 +211,27 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
- (void)layoutSubviews - (void)layoutSubviews
{ {
CGFloat leftInset = 23.0f;
CGFloat rightInset = 66.0f;
CGFloat colorPickerHeight = 10.0f;
if (self.frame.size.width > self.frame.size.height) if (self.frame.size.width > self.frame.size.height)
{ {
if ([_context currentSizeClass] == UIUserInterfaceSizeClassRegular) if ([_context currentSizeClass] == UIUserInterfaceSizeClassRegular)
{ {
_colorPicker.frame = CGRectMake(ceil((self.frame.size.width - TGPhotoPaintSettingsPadPickerWidth) / 2.0f), ceil((self.frame.size.height - 18.0f) / 2.0f), TGPhotoPaintSettingsPadPickerWidth, 18.0f); _colorPicker.frame = CGRectMake(ceil((self.frame.size.width - TGPhotoPaintSettingsPadPickerWidth) / 2.0f), ceil((self.frame.size.height - colorPickerHeight) / 2.0f), TGPhotoPaintSettingsPadPickerWidth, colorPickerHeight);
_settingsButton.frame = CGRectMake(CGRectGetMaxX(_colorPicker.frame) + 11.0f, floor((self.frame.size.height - _settingsButton.frame.size.height) / 2.0f) + 1.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height); _settingsButton.frame = CGRectMake(CGRectGetMaxX(_colorPicker.frame) + 11.0f, floor((self.frame.size.height - _settingsButton.frame.size.height) / 2.0f) + 1.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height);
} }
else else
{ {
_colorPicker.frame = CGRectMake(23.0f, ceil((self.frame.size.height - 18.0f) / 2.0f), self.frame.size.width - 23.0f - 66.0f, 18.0f); _colorPicker.frame = CGRectMake(leftInset, ceil((self.frame.size.height - colorPickerHeight) / 2.0f), self.frame.size.width - leftInset - rightInset, colorPickerHeight);
_eyedropperButton.frame = CGRectMake(10.0f, floor((self.frame.size.height - _eyedropperButton.frame.size.height) / 2.0f) + 1.0f, _eyedropperButton.frame.size.width, _eyedropperButton.frame.size.height);
_settingsButton.frame = CGRectMake(self.frame.size.width - _settingsButton.frame.size.width - 10.0f, floor((self.frame.size.height - _settingsButton.frame.size.height) / 2.0f) + 1.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height); _settingsButton.frame = CGRectMake(self.frame.size.width - _settingsButton.frame.size.width - 10.0f, floor((self.frame.size.height - _settingsButton.frame.size.height) / 2.0f) + 1.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height);
} }
} }
else else
{ {
_colorPicker.frame = CGRectMake(ceil((self.frame.size.width - 18.0f) / 2.0f), 66.0f, 18.0f, self.frame.size.height - 23.0f - 66.0f); _colorPicker.frame = CGRectMake(ceil((self.frame.size.width - colorPickerHeight) / 2.0f), rightInset, colorPickerHeight, self.frame.size.height - leftInset - rightInset);
_eyedropperButton.frame = CGRectMake(floor((self.frame.size.width - _eyedropperButton.frame.size.width) / 2.0f), self.frame.size.height - _eyedropperButton.frame.size.height - 10.0, _eyedropperButton.frame.size.width, _eyedropperButton.frame.size.height);
_settingsButton.frame = CGRectMake(floor((self.frame.size.width - _settingsButton.frame.size.width) / 2.0f), 10.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height); _settingsButton.frame = CGRectMake(floor((self.frame.size.width - _settingsButton.frame.size.width) / 2.0f), 10.0f, _settingsButton.frame.size.width, _settingsButton.frame.size.height);
} }
} }

View File

@ -128,8 +128,8 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
CGSize dimensions = CGSizeZero; CGSize dimensions = CGSizeZero;
if ([self.item isKindOfClass:[TGMediaAsset class]]) if ([self.item isKindOfClass:[TGMediaAsset class]])
dimensions = ((TGMediaAsset *)self.item).dimensions; dimensions = ((TGMediaAsset *)self.item).dimensions;
else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]]) // else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]])
dimensions = [((TGCameraCapturedVideo *)self.item).avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize; // dimensions = [((TGCameraCapturedVideo *)self.item).avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize;
if (!CGSizeEqualToSize(dimensions, CGSizeZero)) if (!CGSizeEqualToSize(dimensions, CGSizeZero))
_quality.maximumValue = [TGMediaVideoConverter bestAvailablePresetForDimensions:dimensions] - 1; _quality.maximumValue = [TGMediaVideoConverter bestAvailablePresetForDimensions:dimensions] - 1;
@ -641,7 +641,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
[self updateInfo]; [self updateInfo];
SSignal *assetSignal = [self.item isKindOfClass:[TGMediaAsset class]] ? [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)self.item] : [SSignal single:((TGCameraCapturedVideo *)self.item).avAsset]; SSignal *assetSignal = [self.item isKindOfClass:[TGMediaAsset class]] ? [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)self.item] : ((TGCameraCapturedVideo *)self.item).avAsset;
if ([self.item isKindOfClass:[TGMediaAsset class]]) if ([self.item isKindOfClass:[TGMediaAsset class]])
[self _updateVideoDuration:((TGMediaAsset *)self.item).videoDuration hasAudio:true]; [self _updateVideoDuration:((TGMediaAsset *)self.item).videoDuration hasAudio:true];

View File

@ -120,7 +120,11 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f;
- (bool)precisePointInside:(CGPoint)point - (bool)precisePointInside:(CGPoint)point
{ {
return [_stickerView pointInside:[_stickerView convertPoint:point fromView:self] withEvent:nil]; CGPoint imagePoint = [_stickerView convertPoint:point fromView:self];
if (![_stickerView pointInside:[_stickerView convertPoint:point fromView:self] withEvent:nil])
return false;
return [_stickerView isOpaqueAtPoint:imagePoint];
} }
- (void)mirror - (void)mirror

View File

@ -184,7 +184,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
{ {
_style = style; _style = style;
switch (_style) { switch (_style) {
case TGPhotoPaintTextEntityStyleClassic: case TGPhotoPaintTextEntityStyleRegular:
_textView.layer.shadowColor = [[UIColor blackColor] CGColor]; _textView.layer.shadowColor = [[UIColor blackColor] CGColor];
_textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f); _textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
_textView.layer.shadowOpacity = 0.4f; _textView.layer.shadowOpacity = 0.4f;
@ -206,7 +206,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
- (void)updateColor - (void)updateColor
{ {
switch (_style) { switch (_style) {
case TGPhotoPaintTextEntityStyleClassic: case TGPhotoPaintTextEntityStyleRegular:
{ {
_textView.textColor = _swatch.color; _textView.textColor = _swatch.color;
_textView.strokeColor = nil; _textView.strokeColor = nil;
@ -214,7 +214,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
} }
break; break;
case TGPhotoPaintTextEntityStyleBorder: case TGPhotoPaintTextEntityStyleOutlined:
{ {
_textView.textColor = [UIColor whiteColor]; _textView.textColor = [UIColor whiteColor];
_textView.strokeColor = _swatch.color; _textView.strokeColor = _swatch.color;
@ -222,7 +222,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
} }
break; break;
case TGPhotoPaintTextEntityStyleFrame: case TGPhotoPaintTextEntityStyleFramed:
{ {
CGFloat lightness = 0.0f; CGFloat lightness = 0.0f;
CGFloat r = 0.0f; CGFloat r = 0.0f;
@ -601,29 +601,14 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
[super showCGGlyphs:glyphs positions:positions count:glyphCount font:font matrix:textMatrix attributes:attributes inContext:context]; [super showCGGlyphs:glyphs positions:positions count:glyphCount font:font matrix:textMatrix attributes:attributes inContext:context];
} }
- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin { - (void)prepare {
[super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
if (self.frameColor != nil) {
NSRange range = [self characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
NSRange glyphRange = [self glyphRangeForCharacterRange:range actualCharacterRange:NULL];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, origin.x, origin.y);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextSetFillColorWithColor(context, self.frameColor.CGColor);
CGContextSetStrokeColorWithColor(context, self.frameColor.CGColor);
_path = nil; _path = nil;
[self.rectArray removeAllObjects]; [self.rectArray removeAllObjects];
[self enumerateLineFragmentsForGlyphRange:glyphRange usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) { [self enumerateLineFragmentsForGlyphRange:NSMakeRange(0, self.textStorage.string) usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
bool ignoreRange = false; bool ignoreRange = false;
NSString *substring = [[self.textStorage string] substringWithRange:glyphRange]; NSRange characterRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
NSString *substring = [[self.textStorage string] substringWithRange:characterRange];
if ([substring stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]].length == 0) { if ([substring stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]].length == 0) {
ignoreRange = true; ignoreRange = true;
} }
@ -636,6 +621,42 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
}]; }];
[self preProccess]; [self preProccess];
}
- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
// [super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
if (self.frameColor != nil) {
NSRange range = [self characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL];
NSRange glyphRange = [self glyphRangeForCharacterRange:range actualCharacterRange:NULL];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, origin.x, origin.y);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextSetFillColorWithColor(context, self.frameColor.CGColor);
CGContextSetStrokeColorWithColor(context, self.frameColor.CGColor);
[self prepare];
// _path = nil;
// [self.rectArray removeAllObjects];
//
// [self enumerateLineFragmentsForGlyphRange:glyphRange usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
// bool ignoreRange = false;
// NSString *substring = [[self.textStorage string] substringWithRange:glyphRange];
// if ([substring stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]].length == 0) {
// ignoreRange = true;
// }
//
// if (!ignoreRange) {
// CGRect newRect = CGRectMake(usedRect.origin.x - self.frameWidthInset, usedRect.origin.y, usedRect.size.width + self.frameWidthInset * 2, usedRect.size.height);
// NSValue *value = [NSValue valueWithCGRect:newRect];
// [self.rectArray addObject:value];
// }
// }];
[self preProccess];
CGRect last = CGRectNull; CGRect last = CGRectNull;
for (int i = 0; i < self.rectArray.count; i ++) { for (int i = 0; i < self.rectArray.count; i ++) {
@ -646,8 +667,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
if (i == 0) { if (i == 0) {
last = cur; last = cur;
} else if (i > 0 && fabs(CGRectGetMaxY(last) - CGRectGetMinY(cur)) < 10.0) { } else if (i > 0 && fabs(CGRectGetMaxY(last) - CGRectGetMinY(cur)) < 10.0) {
NSValue *lastValue = [self.rectArray objectAtIndex:i-1];
last = lastValue.CGRectValue;
CGPoint a = cur.origin; CGPoint a = cur.origin;
CGPoint b = CGPointMake(CGRectGetMaxX(cur), cur.origin.y); CGPoint b = CGPointMake(CGRectGetMaxX(cur), cur.origin.y);
CGPoint c = CGPointMake(last.origin.x, CGRectGetMaxY(last)); CGPoint c = CGPointMake(last.origin.x, CGRectGetMaxY(last));
@ -690,6 +709,8 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
[addPath addLineToPoint:CGPointMake(d.x + _radius, d.y)]; [addPath addLineToPoint:CGPointMake(d.x + _radius, d.y)];
[self.path appendPath:addPath]; [self.path appendPath:addPath];
} }
last = cur;
} }
} }
[self.path fill]; [self.path fill];

View File

@ -8,9 +8,6 @@
@property (nonatomic, copy) void (^fontChanged)(TGPhotoPaintFont *font); @property (nonatomic, copy) void (^fontChanged)(TGPhotoPaintFont *font);
@property (nonatomic, copy) void (^styleChanged)(TGPhotoPaintTextEntityStyle style); @property (nonatomic, copy) void (^styleChanged)(TGPhotoPaintTextEntityStyle style);
@property (nonatomic, strong) TGPhotoPaintFont *font;
@property (nonatomic, assign) TGPhotoPaintTextEntityStyle style;
- (instancetype)initWithFonts:(NSArray *)fonts selectedFont:(TGPhotoPaintFont *)font selectedStyle:(TGPhotoPaintTextEntityStyle)selectedStyle; - (instancetype)initWithFonts:(NSArray *)fonts selectedFont:(TGPhotoPaintFont *)font selectedStyle:(TGPhotoPaintTextEntityStyle)selectedStyle;
@end @end

View File

@ -8,7 +8,7 @@
#import <LegacyComponents/TGModernButton.h> #import <LegacyComponents/TGModernButton.h>
#import "TGPhotoTextEntityView.h" #import "TGPhotoTextEntityView.h"
const CGFloat TGPhotoTextSettingsViewMargin = 19.0f; const CGFloat TGPhotoTextSettingsViewMargin = 10.0f;
const CGFloat TGPhotoTextSettingsItemHeight = 44.0f; const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
@interface TGPhotoTextSettingsView () @interface TGPhotoTextSettingsView ()
@ -17,11 +17,13 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
UIInterfaceOrientation _interfaceOrientation; UIInterfaceOrientation _interfaceOrientation;
UIImageView *_backgroundView; UIView *_wrapperView;
UIView *_contentView;
UIVisualEffectView *_effectView;
NSArray *_fontViews; NSArray *_fontViews;
NSArray *_fontIconViews;
NSArray *_fontSeparatorViews; NSArray *_fontSeparatorViews;
UIImageView *_selectedCheckView;
} }
@end @end
@ -38,95 +40,91 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
_interfaceOrientation = UIInterfaceOrientationPortrait; _interfaceOrientation = UIInterfaceOrientationPortrait;
_backgroundView = [[UIImageView alloc] init]; _wrapperView = [[UIView alloc] init];
_backgroundView.alpha = 0.98f; _wrapperView.clipsToBounds = true;
[self addSubview:_backgroundView]; _wrapperView.layer.cornerRadius = 12.0;
[self addSubview:_wrapperView];
_effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
_effectView.alpha = 0.0f;
_effectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_wrapperView addSubview:_effectView];
_contentView = [[UIView alloc] init];
_contentView.alpha = 0.0f;
_contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_wrapperView addSubview:_contentView];
NSMutableArray *fontViews = [[NSMutableArray alloc] init]; NSMutableArray *fontViews = [[NSMutableArray alloc] init];
NSMutableArray *fontIconViews = [[NSMutableArray alloc] init];
NSMutableArray *separatorViews = [[NSMutableArray alloc] init]; NSMutableArray *separatorViews = [[NSMutableArray alloc] init];
UIFont *font = [UIFont boldSystemFontOfSize:18]; UIFont *font = [UIFont systemFontOfSize:17];
TGModernButton *outlineButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, TGPhotoTextSettingsViewMargin, 0, 0)]; TGModernButton *frameButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
outlineButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
outlineButton.titleLabel.font = font;
outlineButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 44.0f, 0.0f, 0.0f);
outlineButton.tag = TGPhotoPaintTextEntityStyleBorder;
[outlineButton setTitle:@"" forState:UIControlStateNormal];
[outlineButton setTitleColor:[UIColor clearColor]];
[outlineButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:outlineButton];
[fontViews addObject:outlineButton];
TGPhotoTextView *textView = [[TGPhotoTextView alloc] init];
textView.backgroundColor = [UIColor clearColor];
textView.textColor = [UIColor whiteColor];
textView.strokeWidth = 3.0f;
textView.strokeColor = [UIColor blackColor];
textView.strokeOffset = CGPointMake(0.0f, 0.5f);
textView.font = font;
textView.text = TGLocalized(@"Paint.Outlined");
[textView sizeToFit];
textView.frame = CGRectMake(39.0f, ceil((TGPhotoTextSettingsItemHeight - textView.frame.size.height) / 2.0f) - 1.0f, ceil(textView.frame.size.width), ceil(textView.frame.size.height + 0.5f));
[outlineButton addSubview:textView];
UIView *separatorView = [[UIView alloc] init];
separatorView.backgroundColor = UIColorRGB(0xd6d6da);
[self addSubview:separatorView];
[separatorViews addObject:separatorView];
TGModernButton *regularButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, TGPhotoTextSettingsViewMargin + TGPhotoTextSettingsItemHeight, 0, 0)];
regularButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
regularButton.titleLabel.font = font;
regularButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 44.0f, 0.0f, 0.0f);
regularButton.tag = TGPhotoPaintTextEntityStyleClassic;
[regularButton setTitle:TGLocalized(@"Paint.Regular") forState:UIControlStateNormal];
[regularButton setTitleColor:[UIColor blackColor]];
[regularButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:regularButton];
[fontViews addObject:regularButton];
separatorView = [[UIView alloc] init];
separatorView.backgroundColor = UIColorRGB(0xd6d6da);
[self addSubview:separatorView];
[separatorViews addObject:separatorView];
TGModernButton *frameButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, TGPhotoTextSettingsViewMargin + TGPhotoTextSettingsItemHeight + TGPhotoTextSettingsItemHeight, 0, 0)];
frameButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; frameButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
frameButton.titleLabel.font = font; frameButton.titleLabel.font = font;
frameButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 44.0f, 0.0f, 0.0f); frameButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 16.0f, 0.0f, 0.0f);
frameButton.tag = TGPhotoPaintTextEntityStyleFrame; frameButton.tag = TGPhotoPaintTextEntityStyleFramed;
[frameButton setTitle:@"" forState:UIControlStateNormal]; [frameButton setTitle:TGLocalized(@"Paint.Framed") forState:UIControlStateNormal];
[frameButton setTitleColor:[UIColor blackColor]]; [frameButton setTitleColor:[UIColor whiteColor]];
[frameButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside]; [frameButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:frameButton]; [_contentView addSubview:frameButton];
[fontViews addObject:frameButton]; [fontViews addObject:frameButton];
textView = [[TGPhotoTextView alloc] init]; UIImageView *iconView = [[UIImageView alloc] initWithImage:TGTintedImage([UIImage imageNamed:@"Editor/TextFramed"], [UIColor whiteColor])];
textView.backgroundColor = [UIColor clearColor]; [frameButton addSubview:iconView];
textView.textColor = [UIColor whiteColor]; [fontIconViews addObject:iconView];
textView.frameColor = [UIColor blackColor];
textView.font = font; TGModernButton *outlineButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
textView.text = TGLocalized(@"Paint.Framed"); outlineButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
[textView sizeToFit]; outlineButton.titleLabel.font = font;
textView.frame = CGRectMake(39.0f, ceil((TGPhotoTextSettingsItemHeight - textView.frame.size.height) / 2.0f) - 1.0f, ceil(textView.frame.size.width), ceil(textView.frame.size.height + 0.5f)); outlineButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 16.0f, 0.0f, 0.0f);
[frameButton addSubview:textView]; outlineButton.tag = TGPhotoPaintTextEntityStyleOutlined;
[outlineButton setTitle:TGLocalized(@"Paint.Outlined") forState:UIControlStateNormal];
[outlineButton setTitleColor:[UIColor whiteColor]];
[outlineButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside];
[_contentView addSubview:outlineButton];
[fontViews addObject:outlineButton];
iconView = [[UIImageView alloc] initWithImage:TGTintedImage([UIImage imageNamed:@"Editor/TextOutlined"], [UIColor whiteColor])];
[outlineButton addSubview:iconView];
[fontIconViews addObject:iconView];
UIView *separatorView = [[UIView alloc] init];
separatorView.backgroundColor = UIColorRGBA(0xffffff, 0.2);
[_contentView addSubview:separatorView];
[separatorViews addObject:separatorView];
TGModernButton *regularButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
regularButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
regularButton.titleLabel.font = font;
regularButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 16.0f, 0.0f, 0.0f);
regularButton.tag = TGPhotoPaintTextEntityStyleRegular;
[regularButton setTitle:TGLocalized(@"Paint.Regular") forState:UIControlStateNormal];
[regularButton setTitleColor:[UIColor whiteColor]];
[regularButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside];
[_contentView addSubview:regularButton];
[fontViews addObject:regularButton];
iconView = [[UIImageView alloc] initWithImage:TGTintedImage([UIImage imageNamed:@"Editor/TextRegular"], [UIColor whiteColor])];
[regularButton addSubview:iconView];
[fontIconViews addObject:iconView];
separatorView = [[UIView alloc] init];
separatorView.backgroundColor = UIColorRGBA(0xffffff, 0.2);
[_contentView addSubview:separatorView];
[separatorViews addObject:separatorView];
_fontViews = fontViews; _fontViews = fontViews;
_fontIconViews = fontIconViews;
_fontSeparatorViews = separatorViews; _fontSeparatorViews = separatorViews;
_selectedCheckView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PaintCheck")];
_selectedCheckView.frame = CGRectMake(15.0f, 16.0f, _selectedCheckView.frame.size.width, _selectedCheckView.frame.size.height);
[self setStyle:selectedStyle];
} }
return self; return self;
} }
- (void)fontButtonPressed:(TGModernButton *)sender - (void)fontButtonPressed:(TGModernButton *)sender
{ {
[sender addSubview:_selectedCheckView];
if (self.fontChanged != nil) if (self.fontChanged != nil)
self.fontChanged(_fonts[sender.tag]); self.fontChanged(_fonts[sender.tag]);
} }
@ -139,28 +137,22 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
- (void)present - (void)present
{ {
self.alpha = 0.0f; [UIView animateWithDuration:0.25 animations:^
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.2 animations:^
{ {
self.alpha = 1.0f; _effectView.alpha = 1.0f;
_contentView.alpha = 1.0f;
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
self.layer.shouldRasterize = false;
}]; }];
} }
- (void)dismissWithCompletion:(void (^)(void))completion - (void)dismissWithCompletion:(void (^)(void))completion
{ {
self.layer.rasterizationScale = TGScreenScaling(); [UIView animateWithDuration:0.2 animations:^
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.15 animations:^
{ {
self.alpha = 0.0f; _effectView.alpha = 0.0f;
_contentView.alpha = 0.0f;
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
if (completion != nil) if (completion != nil)
@ -168,81 +160,38 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
}]; }];
} }
- (TGPhotoPaintTextEntityStyle)style
{
return (TGPhotoPaintTextEntityStyle)_selectedCheckView.superview.tag;
}
- (void)setStyle:(TGPhotoPaintTextEntityStyle)style
{
[_fontViews[style] addSubview:_selectedCheckView];
}
- (NSString *)font
{
return _fonts[_selectedCheckView.superview.tag];
}
- (void)setFont:(TGPhotoPaintFont *)__unused font
{
}
- (CGSize)sizeThatFits:(CGSize)__unused size - (CGSize)sizeThatFits:(CGSize)__unused size
{ {
return CGSizeMake(256, _fontViews.count * TGPhotoTextSettingsItemHeight + TGPhotoTextSettingsViewMargin * 2); return CGSizeMake(220, _fontViews.count * TGPhotoTextSettingsItemHeight + TGPhotoTextSettingsViewMargin * 2);
} }
- (void)setInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation - (void)setInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{ {
_interfaceOrientation = interfaceOrientation; _interfaceOrientation = interfaceOrientation;
switch (self.interfaceOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
_backgroundView.image = [TGPhotoPaintSettingsView landscapeLeftBackgroundImage];
}
break;
case UIInterfaceOrientationLandscapeRight:
{
_backgroundView.image = [TGPhotoPaintSettingsView landscapeRightBackgroundImage];
}
break;
default:
{
_backgroundView.image = [TGPhotoPaintSettingsView portraitBackgroundImage];
}
break;
}
[self setNeedsLayout]; [self setNeedsLayout];
} }
- (void)layoutSubviews - (void)layoutSubviews
{ {
CGFloat arrowSize = 0.0f;
switch (self.interfaceOrientation) switch (self.interfaceOrientation)
{ {
case UIInterfaceOrientationLandscapeLeft: case UIInterfaceOrientationLandscapeLeft:
{ {
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupLandscapeLeftBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)]; _wrapperView.frame = CGRectMake(TGPhotoTextSettingsViewMargin - arrowSize, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + arrowSize, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
_backgroundView.frame = CGRectMake(TGPhotoTextSettingsViewMargin - 13.0f, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + 13.0f, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
} }
break; break;
case UIInterfaceOrientationLandscapeRight: case UIInterfaceOrientationLandscapeRight:
{ {
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupLandscapeRightBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)]; _wrapperView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + arrowSize, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
_backgroundView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + 13.0f, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
} }
break; break;
default: default:
{ {
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupPortraitBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)]; _wrapperView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2 + arrowSize);
_backgroundView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2 + 13.0f);
} }
break; break;
} }
@ -251,13 +200,17 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
[_fontViews enumerateObjectsUsingBlock:^(TGModernButton *view, NSUInteger index, __unused BOOL *stop) [_fontViews enumerateObjectsUsingBlock:^(TGModernButton *view, NSUInteger index, __unused BOOL *stop)
{ {
view.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin + TGPhotoTextSettingsItemHeight * index, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2, TGPhotoTextSettingsItemHeight); view.frame = CGRectMake(0.0, TGPhotoTextSettingsItemHeight * index, _contentView.frame.size.width, TGPhotoTextSettingsItemHeight);
}];
[_fontIconViews enumerateObjectsUsingBlock:^(UIImageView *view, NSUInteger index, __unused BOOL *stop)
{
view.frame = CGRectMake(_contentView.frame.size.width - 42.0f, (TGPhotoTextSettingsItemHeight - view.frame.size.height) / 2.0, view.frame.size.width, view.frame.size.height);
}]; }];
[_fontSeparatorViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger index, __unused BOOL *stop) [_fontSeparatorViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger index, __unused BOOL *stop)
{ {
view.frame = CGRectMake(TGPhotoTextSettingsViewMargin + 44.0f, TGPhotoTextSettingsViewMargin + TGPhotoTextSettingsItemHeight * (index + 1), self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 - 44.0f, thickness); view.frame = CGRectMake(0.0, TGPhotoTextSettingsItemHeight * (index + 1), _contentView.frame.size.width, thickness);
}]; }];
} }

View File

@ -32,6 +32,7 @@
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName]; TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName];
model.controller = galleryController; model.controller = galleryController;
model.stickersContext = stickersContext;
//model.suggestionContext = self.suggestionContext; //model.suggestionContext = self.suggestionContext;
model.willFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges) model.willFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges)

View File

@ -91,7 +91,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
} else if ([dict[@"type"] isEqualToString:@"text"]) { } else if ([dict[@"type"] isEqualToString:@"text"]) {
UIImage *renderImage = [[UIImage alloc] initWithData:dict[@"data"]]; UIImage *renderImage = [[UIImage alloc] initWithData:dict[@"data"]];
if (renderImage != nil) { if (renderImage != nil) {
TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:nil font:nil swatch:nil baseFontSize:0.0 maxWidth:0.0 style:TGPhotoPaintTextEntityStyleClassic]; TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:nil font:nil swatch:nil baseFontSize:0.0 maxWidth:0.0 style:TGPhotoPaintTextEntityStyleRegular];
entity.uuid = [dict[@"uuid"] integerValue]; entity.uuid = [dict[@"uuid"] integerValue];
entity.position = [dict[@"position"] CGPointValue]; entity.position = [dict[@"position"] CGPointValue];
entity.scale = [dict[@"scale"] floatValue]; entity.scale = [dict[@"scale"] floatValue];

Some files were not shown because too many files have changed in this diff Show More