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
[submodule "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
APP_VERSION="6.2"
APP_VERSION="6.2.1"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)

View File

@ -3,7 +3,7 @@
@implementation Serialization
- (NSUInteger)currentLayer {
return 114;
return 115;
}
- (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.Send" = "Send With Timer";
"Paint.Pen" = "Pen";
"Paint.Marker" = "Marker";
"Paint.Neon" = "Neon";
"Paint.Arrow" = "Arrow";
"Conversation.NoticeInvitedByInChannel" = "%@ invited you to this channel";
"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
}
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 let navigationController: NavigationController
public let chatController: ChatController?
@ -206,12 +216,13 @@ public final class NavigateToChatControllerParams {
public let purposefulAction: (() -> Void)?
public let scrollToEndIfExists: Bool
public let activateMessageSearch: Bool
public let peekData: ChatPeekTimeout?
public let animated: Bool
public let options: NavigationAnimationOptions
public let parentGroupId: PeerGroupId?
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.chatController = chatController
self.context = context
@ -225,6 +236,7 @@ public final class NavigateToChatControllerParams {
self.purposefulAction = purposefulAction
self.scrollToEndIfExists = scrollToEndIfExists
self.activateMessageSearch = activateMessageSearch
self.peekData = peekData
self.animated = animated
self.options = options
self.parentGroupId = parentGroupId

View File

@ -27,7 +27,7 @@ public struct ChatControllerInitialBotStart {
public enum ChatControllerInteractionNavigateToPeer {
case `default`
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?)
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?)
case info
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 {
let controller = standardTextAlertController(theme: alertContext.theme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset)
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, dismissOnOutsideTap: dismissOnOutsideTap)
let presentationDataDisposable = alertContext.themeSignal.start(next: { [weak controller] theme in
controller?.theme = theme
})
@ -36,7 +36,7 @@ public func richTextAlertController(alertContext: AlertControllerContext, title:
}
action.action()
})
}, actionLayout: actionLayout), allowInputInset: allowInputInset)
}, actionLayout: actionLayout, dismissOnOutsideTap: true), allowInputInset: allowInputInset)
dismissImpl = { [weak controller] in
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) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.ready)
self.imageReady.set(self.imageNode.contentReady)
self.imageNode.setSignal(signal |> beforeNext { [weak self] next in
Queue.mainQueue().async {
self?.unroundedImage = next?.1

View File

@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
}, present: { _ 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] = []

View File

@ -485,7 +485,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
})
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):
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
interaction.addContact(phoneNumber)
@ -1385,8 +1385,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
bounds = selectedItemNode.bounds
}
switch item.content {
case let .peer(message, peer, _, _, _, _, _, _, _, _, _, _):
return (selectedItemNode.view, bounds, message?.id ?? peer.peerId)
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _):
return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId)
case let .groupReference(groupId, _, _, _, _):
return (selectedItemNode.view, bounds, groupId)
}

View File

@ -20,7 +20,7 @@ import ChatListSearchItemNode
import ContextUI
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)
public var chatLocation: ChatLocation? {
@ -124,8 +124,8 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
public func selected(listView: ListView) {
switch self.content {
case let .peer(message, peer, _, _, _, _, _, _, promoInfo, _, _, _):
if let message = message, let peer = peer.peer {
case let .peer(messages, peer, _, _, _, _, _, _, promoInfo, _, _, _):
if let message = messages.last, let peer = peer.peer {
self.interaction.messageSelected(peer, message, promoInfo)
} else if let peer = peer.peer {
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 {
var item: ChatListItem?
@ -342,7 +397,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let measureNode: TextNode
private var currentItemHeight: CGFloat?
let textNode: TextNode
let contentImageNode: TransformImageNode
let inputActivitiesNode: ChatListInputActivitiesNode
let dateNode: TextNode
let separatorNode: ASDisplayNode
@ -355,6 +409,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var credibilityIconNode: 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 reorderControlNode: ItemListEditableReorderControlNode?
@ -364,7 +422,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
private var cachedChatListSearchResult: CachedChatListSearchResult?
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 skipFadeout: Bool = false
@ -423,14 +480,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
case .groupReference:
return nil
case let .peer(peer):
if let message = peer.message {
if let message = peer.messages.last {
var result = ""
if message.flags.contains(.Incoming) {
result += "Message"
} else {
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 {
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.displaysAsynchronously = true
self.contentImageNode = TransformImageNode()
self.contentImageNode.isHidden = true
self.inputActivitiesNode = ChatListInputActivitiesNode()
self.inputActivitiesNode.isUserInteractionEnabled = false
self.inputActivitiesNode.alpha = 0.0
@ -517,7 +571,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.contextContainer.addSubnode(self.titleNode)
self.contextContainer.addSubnode(self.authorNode)
self.contextContainer.addSubnode(self.textNode)
self.contextContainer.addSubnode(self.contentImageNode)
self.contextContainer.addSubnode(self.dateNode)
self.contextContainer.addSubnode(self.statusNode)
self.contextContainer.addSubnode(self.pinnedIconNode)
@ -548,9 +601,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var displayAsMessage = false
var enablePreview = true
switch item.content {
case let .peer(message, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
displayAsMessage = displayAsMessageValue
if displayAsMessage, let author = message?.author as? TelegramUser {
if displayAsMessage, let author = messages.last?.author as? TelegramUser {
peer = author
} else {
peer = peerValue.chatMainPeer
@ -674,7 +727,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
let currentItem = self.layoutParams?.0
let currentContentImageMedia = self.contentImageMedia
let currentChatListText = self.cachedChatListText
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 account = item.context.account
var message: Message?
var messages: [Message]
enum ContentPeer {
case chat(RenderedPeer)
case group([ChatListGroupReferencePeer])
@ -706,8 +758,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var groupHiddenByDefault = false
switch item.content {
case let .peer(messageValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, hasFailedMessagesValue):
message = messageValue
case let .peer(messagesValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, hasFailedMessagesValue):
messages = messagesValue
contentPeer = .chat(peerValue)
combinedReadState = combinedReadStateValue
if let combinedReadState = combinedReadState, promoInfoValue == nil && !ignoreUnreadBadge {
@ -729,14 +781,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
isPeerGroup = false
promoInfo = promoInfoValue
displayAsMessage = displayAsMessageValue
hasFailedMessages = messageValue?.flags.contains(.Failed) ?? false // hasFailedMessagesValue
hasFailedMessages = messagesValue.last?.flags.contains(.Failed) ?? false // hasFailedMessagesValue
case let .groupReference(_, peers, messageValue, unreadState, hiddenByDefault):
if let _ = messageValue, !peers.isEmpty {
contentPeer = .chat(peers[0].peer)
} else {
contentPeer = .group(peers)
}
message = messageValue
if let message = messageValue {
messages = [message]
} else {
messages = []
}
combinedReadState = nil
isRemovedFromTotalUnreadCount = false
embeddedState = nil
@ -752,10 +808,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
hasFailedMessages = false
}
if let messageValue = message {
if let messageValue = messages.last {
for media in messageValue.media {
if let media = media as? TelegramMediaAction, case .historyCleared = media.action {
message = nil
messages = []
}
}
}
@ -824,7 +880,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var hideAuthor = false
switch contentPeer {
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 {
initialHideAuthor = true
@ -843,19 +899,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var inlineAuthorPrefix: String?
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 {
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
}
}
}
var contentImageMedia: Media?
var chatListText: (String, String)?
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 {
case let .chat(itemPeer, _, _, text):
var peerText: String?
@ -863,7 +923,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let messagePeer = itemPeer.chatMainPeer {
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 {
} else if !displayAsMessage {
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)
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
if let inlineAuthorPrefix = inlineAuthorPrefix {
composedString = NSMutableAttributedString()
@ -920,28 +980,52 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor)
}
if enableChatListPhotos && !message.containsSecretMedia {
for media in message.media {
if let image = media as? TelegramMediaImage {
textLeftCutout += 26.0
contentImageMedia = image
break
} else if let file = media as? TelegramMediaFile {
if file.isVideo && !file.isInstantVideo {
textLeftCutout += 26.0
contentImageMedia = file
break
}
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let image = content.image {
textLeftCutout += 26.0
contentImageMedia = image
break
} else if let file = content.file {
if file.isVideo && !file.isInstantVideo {
textLeftCutout += 26.0
contentImageMedia = file
break
var displayMediaPreviews = true
if message.containsSecretMedia {
displayMediaPreviews = false
} else if let _ = message.peers[message.id.peerId] as? TelegramSecretChat {
displayMediaPreviews = false
}
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 {
let imageTypes = ["photo", "video", "embed", "gif", "document", "telegram_album"]
if let image = content.image, let type = content.type, imageTypes.contains(type) {
if let _ = largestImageRepresentation(image.representations) {
//let imageSize = largest.dimensions.cgSize
let fitSize = contentImageSize
contentImageSpecs.append((message, image, fitSize))
}
break inner
} else if let file = content.file {
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
//let imageSize = dimensions.cgSize
let fitSize = contentImageSize
contentImageSpecs.append((message, file, fitSize))
}
break inner
}
}
}
@ -980,9 +1064,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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 {
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)
} else if isPeerGroup {
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)
}
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 {
statusState = .clock(PresentationResourcesChatList.clockFrameImage(item.presentationData.theme), PresentationResourcesChatList.clockMinImage(item.presentationData.theme))
} else if message.id.peerId != account.peerId {
@ -1041,7 +1135,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
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 {
let badgeTextColor: UIColor
if unreadCount.muted {
@ -1111,8 +1205,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var credibilityIconOffset: CGFloat = 0.0
if displayAsMessage {
switch item.content {
case let .peer(message, _, _, _, _, _, _, _, _, _, _, _):
if let peer = message?.author {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
if let peer = messages.last?.author {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
@ -1184,7 +1278,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if !textLeftCutout.isZero {
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 (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 = []
}
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)
var animateContent = false
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 contentImageSize = CGSize(width: 18.0, height: 18.0)
var customActions: [ChatListItemAccessibilityCustomAction] = []
for option in peerLeftRevealOptions {
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 {
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
strongSelf.currentItemHeight = itemHeight
strongSelf.contentImageMedia = contentImageMedia
strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult
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 {
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
}
@ -1541,12 +1588,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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)
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
if let inputActivities = inputActivities, !inputActivities.isEmpty {
@ -1586,7 +1627,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
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 {
transition.updateFrame(node: strongSelf.inputActivitiesNode, frame: inputActivitiesFrame)
} else {
@ -1595,6 +1636,43 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
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 {
let titlePosition = strongSelf.titleNode.position
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
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
var contentImageFrame = self.contentImageNode.frame
contentImageFrame.origin = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
transition.updateFrame(node: self.contentImageNode, frame: contentImageFrame)
var mediaPreviewOffset = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0)
let contentImageSpacing: CGFloat = 2.0
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
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 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 message = messages.last
var hideAuthor = false
var messageText: String
if let message = message {
@ -19,151 +54,178 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
peer = chatPeer.chatMainPeer
}
messageText = message.text
messageText = messages[0].text
for media in message.media {
switch media {
case _ as TelegramMediaImage:
if message.text.isEmpty {
messageText = strings.Message_Photo
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if enableMediaEmoji {
messageText = "🖼 \(messageText)"
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 {
switch media {
case _ as TelegramMediaImage:
if message.text.isEmpty {
messageText = strings.Message_Photo
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if enableMediaEmoji {
messageText = "🖼 \(messageText)"
}
}
}
case let fileMedia as TelegramMediaFile:
var processed = false
inner: for attribute in fileMedia.attributes {
switch attribute {
case .Animated:
messageText = strings.Message_Animation
processed = true
break inner
case let .Audio(isVoice, _, title, performer, _):
if !message.text.isEmpty {
messageText = "🎤 \(messageText)"
} else if isVoice {
if message.text.isEmpty {
messageText = strings.Message_Audio
} else {
case let fileMedia as TelegramMediaFile:
var processed = false
inner: for attribute in fileMedia.attributes {
switch attribute {
case .Animated:
messageText = strings.Message_Animation
processed = true
break inner
case let .Audio(isVoice, _, title, performer, _):
if !message.text.isEmpty {
messageText = "🎤 \(messageText)"
}
processed = true
break inner
} else {
let descriptionString: String
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
descriptionString = title + "" + performer
} else if let title = title, !title.isEmpty {
descriptionString = title
} else if let performer = performer, !performer.isEmpty {
descriptionString = performer
} else if let fileName = fileMedia.fileName {
descriptionString = fileName
} else {
descriptionString = strings.Message_Audio
}
messageText = descriptionString
processed = true
break inner
}
case let .Sticker(displayText, _, _):
if displayText.isEmpty {
messageText = strings.Message_Sticker
processed = true
break inner
} else {
messageText = strings.Message_StickerText(displayText).0
processed = true
break inner
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
messageText = strings.Message_VideoMessage
processed = true
break inner
} else {
if message.text.isEmpty {
messageText = strings.Message_Video
processed = true
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if !fileMedia.isAnimated && enableMediaEmoji {
messageText = "📹 \(messageText)"
processed = true
} else if isVoice {
if message.text.isEmpty {
messageText = strings.Message_Audio
} else {
messageText = "🎤 \(messageText)"
}
processed = true
break inner
} else {
let descriptionString: String
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
descriptionString = title + "" + performer
} else if let title = title, !title.isEmpty {
descriptionString = title
} else if let performer = performer, !performer.isEmpty {
descriptionString = performer
} else if let fileName = fileMedia.fileName {
descriptionString = fileName
} else {
descriptionString = strings.Message_Audio
}
messageText = descriptionString
processed = true
break inner
}
case let .Sticker(displayText, _, _):
if displayText.isEmpty {
messageText = strings.Message_Sticker
processed = true
break inner
} else {
messageText = strings.Message_StickerText(displayText).0
processed = true
break inner
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
messageText = strings.Message_VideoMessage
processed = true
break inner
} else {
if message.text.isEmpty {
messageText = strings.Message_Video
processed = true
} else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if enableMediaEmoji {
if !fileMedia.isAnimated {
messageText = "📹 \(messageText)"
}
}
processed = true
break inner
}
}
default:
break
}
}
if !processed {
if !message.text.isEmpty {
messageText = "📎 \(messageText)"
} else {
if fileMedia.isAnimatedSticker {
messageText = strings.Message_Sticker
} else {
if let fileName = fileMedia.fileName {
messageText = fileName
} else {
messageText = strings.Message_File
}
}
}
}
case let location as TelegramMediaMap:
if location.liveBroadcastingTimeout != nil {
messageText = strings.Message_LiveLocation
} else {
messageText = strings.Message_Location
}
case _ as TelegramMediaContact:
messageText = strings.Message_Contact
case let game as TelegramMediaGame:
messageText = "🎮 \(game.title)"
case let invoice as TelegramMediaInvoice:
messageText = invoice.title
case let action as TelegramMediaAction:
switch action.action {
case let .phoneCall(_, discardReason, _):
hideAuthor = !isPeerGroup
let incoming = message.flags.contains(.Incoming)
if let discardReason = discardReason {
switch discardReason {
case .busy, .disconnect:
messageText = strings.Notification_CallCanceled
case .missed:
messageText = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
case .hangup:
break
}
}
if messageText.isEmpty {
if incoming {
messageText = strings.Notification_CallIncoming
} else {
messageText = strings.Notification_CallOutgoing
}
}
default:
break
hideAuthor = true
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
messageText = text
}
}
}
if !processed {
if !message.text.isEmpty {
messageText = "📎 \(messageText)"
} else {
if fileMedia.isAnimatedSticker {
messageText = strings.Message_Sticker
} else {
if let fileName = fileMedia.fileName {
messageText = fileName
} else {
messageText = strings.Message_File
}
}
case _ as TelegramMediaExpiredContent:
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
messageText = text
}
}
case let location as TelegramMediaMap:
if location.liveBroadcastingTimeout != nil {
messageText = strings.Message_LiveLocation
} else {
messageText = strings.Message_Location
}
case _ as TelegramMediaContact:
messageText = strings.Message_Contact
case let game as TelegramMediaGame:
messageText = "🎮 \(game.title)"
case let invoice as TelegramMediaInvoice:
messageText = invoice.title
case let action as TelegramMediaAction:
switch action.action {
case let .phoneCall(_, discardReason, _):
hideAuthor = !isPeerGroup
let incoming = message.flags.contains(.Incoming)
if let discardReason = discardReason {
switch discardReason {
case .busy, .disconnect:
messageText = strings.Notification_CallCanceled
case .missed:
messageText = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
case .hangup:
break
}
}
if messageText.isEmpty {
if incoming {
messageText = strings.Notification_CallIncoming
} else {
messageText = strings.Notification_CallOutgoing
}
}
default:
hideAuthor = true
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
messageText = text
}
}
case _ as TelegramMediaExpiredContent:
if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) {
messageText = text
}
case let poll as TelegramMediaPoll:
messageText = "📊 \(poll.text)"
case let dice as TelegramMediaDice:
messageText = dice.emoji
default:
break
case let poll as TelegramMediaPoll:
messageText = "📊 \(poll.text)"
case let dice as TelegramMediaDice:
messageText = dice.emoji
default:
break
}
}
}
} else {

View File

@ -176,10 +176,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction.additionalCategorySelected(id)
}
), 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 {
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):
let itemPeer = peer.chatMainPeer
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] {
return entries.map { entry -> ListViewUpdateItem in
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 {
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):
let itemPeer = peer.chatMainPeer
var chatPeer: Peer?

View File

@ -46,7 +46,7 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
enum ChatListNodeEntry: Comparable, Identifiable {
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 GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool)
case ArchiveIntro(presentationData: ChatListPresentationData)
@ -98,27 +98,33 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
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 {
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 {
return false
}
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsMessage?.stableVersion != rhsMessage?.stableVersion {
if lhsUnreadCount != rhsUnreadCount {
return false
}
if lhsMessage?.id != rhsMessage?.id || lhsMessage?.flags != rhsMessage?.flags || lhsUnreadCount != rhsUnreadCount {
if lhsMessages.count != rhsMessages.count {
return false
}
if let lhsMessage = lhsMessage, let rhsMessage = rhsMessage {
if lhsMessage.associatedMessages.count != rhsMessage.associatedMessages.count {
for i in 0 ..< lhsMessages.count {
if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion {
return false
}
for (id, message) in lhsMessage.associatedMessages {
if let otherMessage = rhsMessage.associatedMessages[id] {
if lhsMessages[i].id != rhsMessages[i].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 {
return false
}
@ -295,20 +301,20 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
var filterAfterHole = false
loop: for entry in view.entries {
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 {
continue loop
}
if state.pendingRemovalPeerIds.contains(index.messageIndex.id.peerId) {
continue loop
}
var updatedMessage = message
var updatedMessages = messages
var updatedCombinedReadState = combinedReadState
if state.pendingClearHistoryPeerIds.contains(index.messageIndex.id.peerId) {
updatedMessage = nil
updatedMessages = []
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):
if hole.index.timestamp == Int32.max - 1 {
//return ([.HeaderEntry], true)
@ -321,7 +327,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
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 {
if !filteredAdditionalItemEntries.isEmpty {
for item in filteredAdditionalItemEntries.reversed() {
@ -336,8 +342,8 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
promoInfo = .psa(type: type, message: message)
}
switch item.entry {
case let .MessageEntry(index, message, 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))
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, 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 {
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
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)
})))
@ -58,7 +58,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|> deliverOnMainQueue).start(next: { currentPeerId in
if let currentPeerId = currentPeerId {
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 {
var createSignal = createSecretChat(account: context.account, peerId: peerId)
@ -93,7 +93,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
createSecretChatDisposable.set((createSignal
|> deliverOnMainQueue).start(next: { peerId in
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
if let contactsController = contactsController {

View File

@ -127,6 +127,12 @@ public class ImageNode: ASDisplayNode {
private var first = true
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> {
if let hasImage = self.hasImage {
return hasImage.get()
@ -171,6 +177,10 @@ public class ImageNode: ASDisplayNode {
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 let _dismissOnOutsideTap: Bool
override public var dismissOnOutsideTap: Bool {
return self._dismissOnOutsideTap
}
public var textAttributeAction: (NSAttributedString.Key, (Any) -> Void)? {
didSet {
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.actionLayout = actionLayout
self._dismissOnOutsideTap = dismissOnOutsideTap
if let title = title {
let titleNode = ImmediateTextNode()
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 {
return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions, actionLayout: actionLayout))
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, 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)?
let attributedText: NSAttributedString
if parseMarkdown {
@ -385,7 +391,7 @@ public func standardTextAlertController(theme: AlertControllerTheme, title: Stri
dismissImpl?()
action.action()
})
}, actionLayout: actionLayout), allowInputInset: allowInputInset)
}, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap), allowInputInset: allowInputInset)
dismissImpl = { [weak controller] in
controller?.dismissAnimated()
}

View File

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

View File

@ -1168,9 +1168,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.loadProgress.set(1.0)
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), openPeer: { peerId, navigation in
switch navigation {
case let .chat(_, subject):
case let .chat(_, subject, peekData):
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):
if let navigationController = strongSelf.getNavigationController() {

View File

@ -21,14 +21,14 @@ public final class JoinLinkPreviewController: ViewController {
private let context: AccountContext
private let link: String
private let navigateToPeer: (PeerId) -> Void
private let navigateToPeer: (PeerId, ChatPeekTimeout?) -> Void
private let parentNavigationController: NavigationController?
private var resolvedState: ExternalJoiningChatState?
private var presentationData: PresentationData
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.link = link
self.navigateToPeer = navigateToPeer
@ -81,7 +81,10 @@ public final class JoinLinkPreviewController: ViewController {
let data = JoinLinkPreviewData(isGroup: participants != nil, isJoined: false)
strongSelf.controllerNode.setPeer(image: photoRepresentation, title: title, memberCount: participantsCount, members: participants ?? [], data: data)
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()
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))
@ -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
if let strongSelf = self {
if let peerId = peerId {
strongSelf.navigateToPeer(peerId)
strongSelf.navigateToPeer(peerId, nil)
strongSelf.dismiss()
}
}

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "ic_editor_font.pdf",
"filename" : "ic_menu_brush4.pdf",
"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>
@class AVURLAsset;
@class TGMediaAsset;
@interface TGCameraCapturedVideo : NSObject <TGMediaEditableItem, TGMediaSelectableItem>
@property (nonatomic, readonly) AVURLAsset *avAsset;
@property (nonatomic, readonly) SSignal *avAsset;
@property (nonatomic, readonly) NSTimeInterval videoDuration;
@property (nonatomic, readonly) bool isAnimation;
@property (nonatomic, readonly) TGMediaAsset *originalAsset;
- (instancetype)initWithURL:(NSURL *)url;
- (instancetype)initWithAsset:(TGMediaAsset *)asset;
- (void)_cleanUp;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -178,7 +178,13 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
if (strongSelf == nil)
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 updateSelectionIndexes];
@ -917,7 +923,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset];
return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else {
return [editableItem originalImageSignal:position];
}

View File

@ -3,21 +3,49 @@
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import "LegacyComponentsGlobals.h"
#import "TGStringUtils.h"
#import "TGMediaAsset.h"
#import "TGMediaAsset+TGMediaEditableItem.h"
#import "TGGifConverter.h"
@interface TGCameraCapturedVideo ()
{
CGSize _cachedSize;
NSTimeInterval _cachedDuration;
AVURLAsset *_cachedAVAsset;
}
@end
@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
{
self = [super init];
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;
_cachedDuration = 0.0;
}
@ -26,7 +54,9 @@
- (void)_cleanUp
{
[[NSFileManager defaultManager] removeItemAtPath:_avAsset.URL.path error:nil];
if (_originalAsset == nil) {
[[NSFileManager defaultManager] removeItemAtPath:_cachedAVAsset.URL.path error:nil];
}
}
- (bool)isVideo
@ -34,9 +64,47 @@
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
{
return _avAsset.URL.absoluteString;
if (_originalAsset) {
return _originalAsset.uniqueIdentifier;
} else {
return _cachedAVAsset.URL.absoluteString;
}
}
- (CGSize)originalSize
@ -44,7 +112,11 @@
if (!CGSizeEqualToSize(_cachedSize, CGSizeZero))
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;
return _cachedSize;
}
@ -59,29 +131,41 @@
if (_cachedDuration > DBL_EPSILON)
return _cachedDuration;
_cachedDuration = CMTimeGetSeconds(_avAsset.duration);
if (_cachedAVAsset != nil) {
_cachedDuration = CMTimeGetSeconds(_cachedAVAsset.duration);
}
return _cachedDuration;
}
- (SSignal *)thumbnailImageSignal
{
CGFloat thumbnailImageSide = TGPhotoEditorScreenImageMaxSize().width;
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(thumbnailImageSide, thumbnailImageSide));
if (_originalAsset != nil) {
return [_originalAsset thumbnailImageSignal];
} else {
CGFloat thumbnailImageSide = TGPhotoEditorScreenImageMaxSize().width;
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
{
CGFloat imageSide = 1280.0f;
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(imageSide, imageSide));
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_avAsset size:size timestamp:kCMTimeZero];
if (_originalAsset != nil) {
return [_originalAsset screenImageSignal:position];
} else {
CGFloat imageSide = 1280.0f;
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(imageSide, imageSide));
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:_cachedAVAsset size:size timestamp:kCMTimeZero];
}
}
- (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

View File

@ -1772,7 +1772,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return [SSignal single:((TGCameraCapturedVideo *)editableItem).avAsset];
return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else {
return [editableItem originalImageSignal:position];
}
@ -2467,7 +2467,9 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
{
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];
@ -2689,9 +2691,11 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
};
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);
}];
}];
SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image)
@ -2709,7 +2713,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict[@"type"] = @"cameraVideo";
dict[@"url"] = video.avAsset.URL;
// dict[@"url"] = video.avAsset.URL;
dict[@"previewImage"] = image;
dict[@"adjustments"] = adjustments;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];

View File

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

View File

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

View File

@ -77,7 +77,11 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
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 = @
{
@ -122,8 +126,8 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
NSDictionary *attributes = @
{
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32ARGB),
(NSString *)kCVPixelBufferWidthKey : @(sourceWidth),
(NSString *)kCVPixelBufferHeightKey : @(sourceHeight),
(NSString *)kCVPixelBufferWidthKey : @(renderWidth),
(NSString *)kCVPixelBufferHeightKey : @(renderHeight),
(NSString *)kCVPixelBufferCGImageCompatibilityKey : @YES,
(NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES
};
@ -148,7 +152,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
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 (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);
size_t width = CGImageGetWidth(frame);
size_t height = CGImageGetHeight(frame);
size_t bpc = 8;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CVPixelBufferRef pxBuffer = NULL;
@ -246,7 +247,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
if (pixelBufferPool)
status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pxBuffer);
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");
@ -255,10 +256,10 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
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");
CGContextDrawImage(context, CGRectMake(0, 0, width, height), frame);
CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), frame);
CVPixelBufferUnlockBaseAddress(pxBuffer, 0);

View File

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

View File

@ -21,6 +21,7 @@
#import "TGModernGallerySelectableItem.h"
#import "TGModernGalleryEditableItem.h"
#import "TGMediaPickerGalleryPhotoItem.h"
#import "TGMediaPickerGalleryVideoItem.h"
#import "TGMediaPickerGalleryPhotoItemView.h"
#import "TGMediaPickerGalleryVideoItemView.h"
@ -150,6 +151,8 @@
[strongSelf.window endEditing:true];
if (strongSelf->_doneLongPressed != nil)
strongSelf->_doneLongPressed(strongSelf->_currentItem);
[[NSUserDefaults standardUserDefaults] setObject:@(3) forKey:@"TG_displayedMediaTimerTooltip_v3"];
};
_muteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 39.0f, 39.0f)];
@ -571,7 +574,15 @@
[strongSelf->_portraitToolbarView setEditButtonsEnabled:available animated:true];
[strongSelf->_landscapeToolbarView setEditButtonsEnabled:available animated:true];
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;
}
}]];
@ -911,7 +922,7 @@
- (bool)shouldDisplayTooltip
{
return ![[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedMediaTimerTooltip_v2"] boolValue];
return [[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedMediaTimerTooltip_v3"] intValue] < 3;
}
- (void)setupTooltip:(CGRect)rect
@ -919,7 +930,7 @@
if (_tooltipContainerView != nil || !_hasTimer)
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)];
[self addSubview:_tooltipContainerView];
@ -934,7 +945,8 @@
[_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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -370,13 +370,18 @@
case TGMediaAssetGifType:
{
// TGCameraCapturedVideo *convertedAsset = [[TGCameraCapturedVideo alloc] initWithAsset:asset];
// galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:convertedAsset];
galleryItem = [[TGMediaPickerGalleryGifItem alloc] initWithAsset:asset];
}
break;
default:
{
galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:asset];
if (asset.subtypes & TGMediaAssetSubtypePhotoLive)
galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:asset];
else
galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:asset];
}
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.backgroundColor = [UIColor clearColor];
_countLabel.font = [TGFont roundedFontOfSize:17];
_countLabel.font = [TGFont roundedFontOfSize:18];
_countLabel.text = [TGStringUtils stringWithLocalizedNumber:0];
_countLabel.textColor = [UIColor whiteColor];
[_wrapperView addSubview:_countLabel];
@ -299,15 +299,15 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
{
labelOrigin = 12 + TGScreenPixel + (38 - labelWidth) / 2;
if ([_countLabel.text isEqualToString:@"1"] || [_countLabel.text isEqualToString:@"4"])
labelOrigin -= 2 * TGScreenPixel;
// if ([_countLabel.text isEqualToString:@"1"] || [_countLabel.text isEqualToString:@"4"])
// labelOrigin -= 2 * TGScreenPixel;
}
else
{
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;
}

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 angle;
@property (nonatomic, readonly) CGFloat scale;
@property (nonatomic, readonly) CGFloat dynamic;
@property (nonatomic, readonly) bool lightSaber;
@property (nonatomic, readonly) bool arrow;
@property (nonatomic, readonly) CGImageRef stampRef;
@property (nonatomic, readonly) CGImageRef previewStampRef;
@ -20,4 +22,4 @@
@end
extern const CGSize TGPaintBrushTextureSize;
extern const CGSize TGPaintBrushPreviewTextureSize;
extern const CGSize TGPaintBrushPreviewTextureSize;

View File

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

View File

@ -283,10 +283,11 @@ const NSUInteger TGPaintBrushPreviewSegmentsCount = 100;
[self _setupBrush];
[_renderState reset];
_path.remainder = 0.0f;
_path.pressureRemainder = 0.0f;
_path.brush = brush;
[TGPaintRender renderPath:_path renderState:_renderState];
if (_brush.lightSaber)
{
glBindFramebuffer(GL_FRAMEBUFFER, _lightFramebuffer);

View File

@ -9,6 +9,7 @@
#import "TGPaintPath.h"
#import "TGPaintState.h"
#import "TGPaintCanvas.h"
#import "TGPaintBrush.h"
#import <LegacyComponents/TGPaintUtils.h>
@interface TGPaintInput ()
@ -19,6 +20,8 @@
CGPoint _lastLocation;
CGFloat _lastRemainder;
CGFloat _lastPressureRemainder;
CGFloat _lastAngle;
TGPaintPoint *_points[3];
NSInteger _pointsCount;
@ -48,7 +51,7 @@
CGPoint midPoint1 = TGPaintMultiplyPoint(TGPaintAddPoints(prev1.CGPoint, prev2.CGPoint), 0.5f);
CGFloat midPressure1 = (prev1.z + prev2.z) * 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;
CGFloat distance = TGPaintDistance(midPoint1, midPoint2);
@ -126,12 +129,15 @@
if (_pointsCount != 0)
pressure = (pressure + _points[_pointsCount - 1].z) / 2.0f;
pressure = 1.0f;
TGPaintPoint *point = [TGPaintPoint pointWithX:location.x y:location.y z:pressure];
_points[_pointsCount++] = point;
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];
_moved = true;
}
@ -156,6 +162,22 @@
else
{
[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;
@ -181,16 +203,20 @@
path.brush = canvas.state.brush;
path.baseWeight = canvas.state.weight;
if (_clearBuffer)
if (_clearBuffer) {
_lastRemainder = 0.0f;
_lastPressureRemainder = 0.0f;
}
path.remainder = _lastRemainder;
path.pressureRemainder = _lastPressureRemainder;
[canvas.painting paintStroke:path clearBuffer:_clearBuffer completion:^
{
TGDispatchOnMainThread(^
{
_lastRemainder = path.remainder;
_lastPressureRemainder = path.pressureRemainder;
_clearBuffer = false;
});
}];

View File

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

View File

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

View File

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

View File

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

View File

@ -11,14 +11,14 @@
return false;
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
{
TGPaintSwatch *swatch = [[TGPaintSwatch alloc] init];
swatch->_color = color;
swatch->_colorLocaton = colorLocation;
swatch->_colorLocation = colorLocation;
swatch->_brushWeight = brushWeight;
return swatch;

View File

@ -10,19 +10,20 @@
#import "TGPaintBrush.h"
#import "TGPaintBrushPreview.h"
const CGFloat TGPhotoBrushSettingsViewMargin = 19.0f;
const CGFloat TGPhotoBrushSettingsViewMargin = 10.0f;
const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
@interface TGPhotoBrushSettingsView ()
{
NSArray *_brushes;
TGPaintBrushPreview *_preview;
UIImageView *_backgroundView;
UIView *_wrapperView;
UIView *_contentView;
UIVisualEffectView *_effectView;
NSArray *_brushViews;
NSArray *_brushIconViews;
NSArray *_brushSeparatorViews;
UIImageView *_selectedCheckView;
UIImage *_landscapeLeftBackgroundImage;
UIImage *_landscapeRightBackgroundImage;
@ -40,80 +41,110 @@ const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
if (self != nil)
{
_brushes = brushes;
_preview = preview;
_interfaceOrientation = UIInterfaceOrientationPortrait;
_backgroundView = [[UIImageView alloc] init];
//_backgroundView.alpha = 0.98f;
[self addSubview:_backgroundView];
_wrapperView = [[UIView alloc] init];
_wrapperView.clipsToBounds = true;
_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 *brushIconViews = [[NSMutableArray alloc] init];
NSMutableArray *separatorViews = [[NSMutableArray alloc] init];
[brushes enumerateObjectsUsingBlock:^(__unused TGPaintBrush *brush, NSUInteger index, __unused BOOL *stop)
{
TGModernButton *button = [[TGModernButton alloc] initWithFrame:CGRectMake(0, TGPhotoBrushSettingsViewMargin + index * TGPhotoBrushSettingsItemHeight, 0, 0)];
button.tag = index;
button.imageView.contentMode = UIViewContentModeCenter;
button.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 30.0f, 0.0f, 0.0f);
[button addTarget:self action:@selector(brushButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
NSString *title;
UIImage *icon;
switch (index) {
case 0:
title = TGLocalized(@"Paint.Pen");
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];
UIImageView *iconView = [[UIImageView alloc] initWithImage:TGTintedImage(icon, [UIColor whiteColor])];
[button addSubview:iconView];
[brushIconViews addObject:iconView];
if (index != brushes.count - 1)
{
UIView *separatorView = [[UIView alloc] init];
separatorView.backgroundColor = UIColorRGB(0xd6d6da);
[self addSubview:separatorView];
separatorView.backgroundColor = UIColorRGBA(0xffffff, 0.2);
[_contentView addSubview:separatorView];
[separatorViews addObject:separatorView];
}
}];
_brushViews = brushViews;
_brushIconViews = brushIconViews;
_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;
}
- (void)brushButtonPressed:(TGModernButton *)sender
{
[sender addSubview:_selectedCheckView];
if (self.brushChanged != nil)
self.brushChanged(_brushes[sender.tag]);
}
- (void)present
{
self.alpha = 0.0f;
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[self _setupBrushPreviews];
[UIView animateWithDuration:0.2 animations:^
[UIView animateWithDuration:0.25 animations:^
{
self.alpha = 1.0f;
_effectView.alpha = 1.0f;
_contentView.alpha = 1.0f;
} completion:^(__unused BOOL finished)
{
//self.layer.shouldRasterize = false;
}];
}
- (void)dismissWithCompletion:(void (^)(void))completion
{
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.15 animations:^
[UIView animateWithDuration:0.2 animations:^
{
self.alpha = 0.0f;
_effectView.alpha = 0.0f;
_contentView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
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
{
return CGSizeMake(256, _brushViews.count * TGPhotoBrushSettingsItemHeight + TGPhotoBrushSettingsViewMargin * 2);
return CGSizeMake(220, _brushViews.count * TGPhotoBrushSettingsItemHeight + TGPhotoBrushSettingsViewMargin * 2);
}
- (void)setInterfaceOrientation:(UIInterfaceOrientation)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];
}
- (void)layoutSubviews
{
CGFloat arrowSize = 0.0f;
switch (self.interfaceOrientation)
{
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;
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;
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;
}
@ -213,13 +192,17 @@ const CGFloat TGPhotoBrushSettingsItemHeight = 44.0f;
[_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)
{
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]])
assetSignal = [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)item];
else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
assetSignal = [SSignal single:((TGCameraCapturedVideo *)item).avAsset];
assetSignal = ((TGCameraCapturedVideo *)item).avAsset;
[assetSignal startWithNext:^(AVAsset *asset)
{

View File

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

View File

@ -47,7 +47,7 @@
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:self];
CGPoint point = [gestureRecognizer locationInView:self];
NSMutableArray *intersectedViews = [[NSMutableArray alloc] init];
for (TGPhotoPaintEntityView *view in self.subviews)
@ -55,7 +55,7 @@
if (![view isKindOfClass:[TGPhotoPaintEntityView class]])
continue;
if ([view pointInside:[view convertPoint:location fromView:self] withEvent:nil])
if ([view pointInside:[view convertPoint:point fromView:self] withEvent:nil])
[intersectedViews addObject:view];
}
@ -65,7 +65,7 @@
__block TGPhotoPaintEntityView *subresult = nil;
[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;
*stop = true;
@ -83,6 +83,40 @@
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
{
return MAX(0, (NSInteger)self.subviews.count - 1);

View File

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

View File

@ -23,6 +23,7 @@
#import "TGPaintRadialBrush.h"
#import "TGPaintEllipticalBrush.h"
#import "TGPaintNeonBrush.h"
#import "TGPaintArrowBrush.h"
#import "TGPaintCanvas.h"
#import "TGPaintingWrapperView.h"
#import "TGPaintState.h"
@ -45,6 +46,7 @@
#import "TGPhotoEntitiesContainerView.h"
#import "TGPhotoStickerEntityView.h"
#import "TGPhotoTextEntityView.h"
#import "TGPhotoPaintEyedropperView.h"
#import "TGPaintFaceDetector.h"
#import "TGPhotoMaskPosition.h"
@ -83,6 +85,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
UIView *_contentWrapperView;
UIView *_dimView;
TGModernButton *_doneButton;
TGPhotoPaintActionsView *_landscapeActionsView;
TGPhotoPaintActionsView *_portraitActionsView;
@ -104,6 +107,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
TGPhotoPaintSelectionContainerView *_selectionContainerView;
TGPhotoPaintEntitySelectionView *_entitySelectionView;
TGPhotoPaintEyedropperView *_eyedropperView;
TGPhotoTextEntityView *_editedTextView;
CGPoint _editedTextCenter;
@ -122,6 +126,11 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
bool _enableStickers;
NSData *_eyedropperBackgroundData;
CGSize _eyedropperBackgroundSize;
NSInteger _eyedropperBackgroundBytesPerRow;
CGBitmapInfo _eyedropperBackgroundInfo;
id<LegacyComponentsContext> _context;
}
@ -151,10 +160,11 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[
[[TGPaintRadialBrush alloc] init],
[[TGPaintEllipticalBrush alloc] init],
[[TGPaintNeonBrush alloc] init]
[[TGPaintNeonBrush alloc] init],
[[TGPaintArrowBrush alloc] init],
];
_selectedTextFont = [[TGPhotoPaintFont availableFonts] firstObject];
_selectedTextStyle = TGPhotoPaintTextEntityStyleBorder;
_selectedTextStyle = TGPhotoPaintTextEntityStyleFramed;
if (_photoEditor.paintingData.undoManager != nil)
_undoManager = [_photoEditor.paintingData.undoManager copy];
@ -265,11 +275,30 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_dimView.backgroundColor = UIColorRGBA(0x000000, 0.4f);
_dimView.userInteractionEnabled = false;
[_entitiesContainerView addSubview:_dimView];
_selectionContainerView = [[TGPhotoPaintSelectionContainerView alloc] init];
_selectionContainerView.clipsToBounds = false;
[_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];
[self.view addSubview:_wrapperView];
@ -307,12 +336,22 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_landscapeActionsView.clearPressed = clearPressed;
[_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) = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf commitEyedropper:true];
if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
[strongSelf presentTextSettingsView];
else if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]])
@ -321,11 +360,22 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[strongSelf presentBrushSettingsView];
};
void (^eyedropperPressed)(void) = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[self enableEyedropper];
};
void (^beganColorPicking)(void) = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf commitEyedropper:true];
if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
[strongSelf setDimHidden:false animated:true];
@ -346,6 +396,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (strongSelf == nil)
return;
[strongSelf commitEyedropper:true];
[strongSelf setCurrentSwatch:swatch sender:sender];
if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
@ -353,6 +405,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
};
_portraitSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context];
_portraitSettingsView.eyedropperPressed = eyedropperPressed;
_portraitSettingsView.beganColorPicking = beganColorPicking;
_portraitSettingsView.changedColor = changedColor;
_portraitSettingsView.finishedColorPicking = finishedColorPicking;
@ -362,6 +415,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[_portraitToolsWrapperView addSubview:_portraitSettingsView];
_landscapeSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context];
_landscapeSettingsView.eyedropperPressed = eyedropperPressed;
_landscapeSettingsView.beganColorPicking = beganColorPicking;
_landscapeSettingsView.changedColor = changedColor;
_landscapeSettingsView.finishedColorPicking = finishedColorPicking;
@ -489,7 +543,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (TGPhotoEditorTab)availableTabs
{
TGPhotoEditorTab result = TGPhotoEditorPaintTab | TGPhotoEditorEraserTab | TGPhotoEditorTextTab;
if (_enableStickers) {
if (_enableStickers && _stickersContext != nil) {
result |= TGPhotoEditorStickerTab;
}
return result;
@ -497,6 +551,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (void)handleTabAction:(TGPhotoEditorTab)tab
{
[self commitEyedropper:true];
switch (tab)
{
case TGPhotoEditorStickerTab:
@ -625,6 +681,49 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
#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
{
if (_resultData != nil)
@ -698,6 +797,60 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
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
- (void)selectEntityView:(TGPhotoPaintEntityView *)view
@ -734,7 +887,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if ([view isKindOfClass:[TGPhotoTextEntityView class]])
{
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];
@ -1061,7 +1214,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch;
TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:[UIColor whiteColor] colorLocation:1.0f 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;
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
{
if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]])
[self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconText];
else if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]])
if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) {
TGPhotoPaintSettingsViewIcon icon;
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];
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];
}
@ -1347,12 +1522,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (strongSelf == nil)
return;
if (strongSelf->_canvasView.state.eraser && brush.lightSaber)
if (strongSelf->_canvasView.state.eraser && (brush.lightSaber || brush.arrow))
brush = strongSelf->_brushes.firstObject;
[strongSelf->_canvasView setBrush:brush];
[strongSelf settingsWrapperPressed];
[strongSelf updateSettingsButton];
};
_settingsView = view;
[view sizeToFit];
@ -1383,6 +1559,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[textView setFont:font];
[strongSelf settingsWrapperPressed];
[strongSelf updateSettingsButton];
};
view.styleChanged = ^(TGPhotoPaintTextEntityStyle style)
{
@ -1392,13 +1569,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
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 *blackSwatch = [TGPaintSwatch swatchWithColor:[UIColor blackColor] colorLocation:0.85f brushWeight:currentSwatch.brushWeight];
[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 *whiteSwatch = [TGPaintSwatch swatchWithColor:[UIColor whiteColor] colorLocation:1.0f brushWeight:currentSwatch.brushWeight];
@ -1409,6 +1586,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[textView setStyle:style];
[strongSelf settingsWrapperPressed];
[strongSelf updateSettingsButton];
};
_settingsView = view;
@ -1429,13 +1607,14 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
if (_canvasView.state.eraser)
{
if (_canvasView.state.brush.lightSaber)
if (_canvasView.state.brush.lightSaber || _canvasView.state.brush.arrow)
[_canvasView setBrush:_brushes.firstObject];
}
[_portraitSettingsView setHighlighted:_canvasView.state.isEraser];
[_landscapeSettingsView setHighlighted:_canvasView.state.isEraser];
[self updateSettingsButton];
[self _updateTabs];
}
@ -1934,17 +2113,22 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[_dimView.superview insertSubview:_dimView belowSubview:_currentEntityView];
else
[_dimView.superview bringSubviewToFront:_dimView];
[_doneButton.superview bringSubviewToFront:_doneButton];
}
else
{
[_entitySelectionView fadeIn];
[_dimView.superview bringSubviewToFront:_dimView];
[_doneButton.superview bringSubviewToFront:_doneButton];
}
void (^changeBlock)(void) = ^
{
_dimView.alpha = hidden ? 0.0f : 1.0f;
_doneButton.alpha = hidden ? 0.0f : 1.0f;
};
if (animated)
@ -2016,6 +2200,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_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)
[_settingsView setInterfaceOrientation:orientation];
@ -2151,6 +2337,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation);
_selectionContainerView.frame = previewFrame;
_eyedropperView.frame = _selectionContainerView.bounds;
_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
@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
{
TGPhotoPaintSettingsViewIconBrush,
TGPhotoPaintSettingsViewIconText,
TGPhotoPaintSettingsViewIconBrushPen,
TGPhotoPaintSettingsViewIconBrushMarker,
TGPhotoPaintSettingsViewIconBrushNeon,
TGPhotoPaintSettingsViewIconBrushArrow,
TGPhotoPaintSettingsViewIconTextRegular,
TGPhotoPaintSettingsViewIconTextOutlined,
TGPhotoPaintSettingsViewIconTextFramed,
TGPhotoPaintSettingsViewIconMirror
} TGPhotoPaintSettingsViewIcon;
@ -17,7 +22,9 @@ typedef enum
@property (nonatomic, copy) void (^changedColor)(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, readonly) UIButton *settingsButton;
@property (nonatomic, strong) TGPaintSwatch *swatch;

View File

@ -14,6 +14,7 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
@interface TGPhotoPaintSettingsView ()
{
TGPhotoPaintColorPicker *_colorPicker;
TGModernButton *_eyedropperButton;
TGModernButton *_settingsButton;
TGPhotoPaintSettingsViewIcon _icon;
@ -54,7 +55,13 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
};
[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.exclusiveTouch = true;
@ -85,6 +92,12 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
_colorPicker.orientation = interfaceOrientation;
}
- (void)eyedropperButtonPressed
{
if (self.eyedropperPressed != nil)
self.eyedropperPressed();
}
- (void)settingsButtonPressed
{
if (self.settingsPressed != nil)
@ -143,25 +156,35 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
- (UIImage *)_imageForIcon:(TGPhotoPaintSettingsViewIcon)icon highlighted:(bool)highlighted
{
UIColor *color = highlighted ? [TGPhotoEditorInterfaceAssets accentColor] : [UIColor whiteColor];
UIImage *iconImage = nil;
switch (icon)
{
case TGPhotoPaintSettingsViewIconBrush:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Brush"], [UIColor whiteColor]);
case TGPhotoPaintSettingsViewIconBrushPen:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedPen"], color);
break;
case TGPhotoPaintSettingsViewIconText:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Font"], [UIColor whiteColor]);
case TGPhotoPaintSettingsViewIconBrushMarker:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/BrushSelectedMarker"], color);
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;
case TGPhotoPaintSettingsViewIconMirror:
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Flip"], [UIColor whiteColor]);
iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Flip"], color);
break;
}
if (highlighted)
iconImage = TGTintedImage(iconImage, [TGPhotoEditorInterfaceAssets accentColor]);
return iconImage;
}
@ -188,22 +211,27 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f;
- (void)layoutSubviews
{
CGFloat leftInset = 23.0f;
CGFloat rightInset = 66.0f;
CGFloat colorPickerHeight = 10.0f;
if (self.frame.size.width > self.frame.size.height)
{
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);
}
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);
}
}
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);
}
}

View File

@ -128,8 +128,8 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
CGSize dimensions = CGSizeZero;
if ([self.item isKindOfClass:[TGMediaAsset class]])
dimensions = ((TGMediaAsset *)self.item).dimensions;
else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]])
dimensions = [((TGCameraCapturedVideo *)self.item).avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize;
// else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]])
// dimensions = [((TGCameraCapturedVideo *)self.item).avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize;
if (!CGSizeEqualToSize(dimensions, CGSizeZero))
_quality.maximumValue = [TGMediaVideoConverter bestAvailablePresetForDimensions:dimensions] - 1;
@ -641,7 +641,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
[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]])
[self _updateVideoDuration:((TGMediaAsset *)self.item).videoDuration hasAudio:true];

View File

@ -120,7 +120,11 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f;
- (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

View File

@ -184,7 +184,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
{
_style = style;
switch (_style) {
case TGPhotoPaintTextEntityStyleClassic:
case TGPhotoPaintTextEntityStyleRegular:
_textView.layer.shadowColor = [[UIColor blackColor] CGColor];
_textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
_textView.layer.shadowOpacity = 0.4f;
@ -206,7 +206,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
- (void)updateColor
{
switch (_style) {
case TGPhotoPaintTextEntityStyleClassic:
case TGPhotoPaintTextEntityStyleRegular:
{
_textView.textColor = _swatch.color;
_textView.strokeColor = nil;
@ -214,7 +214,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
}
break;
case TGPhotoPaintTextEntityStyleBorder:
case TGPhotoPaintTextEntityStyleOutlined:
{
_textView.textColor = [UIColor whiteColor];
_textView.strokeColor = _swatch.color;
@ -222,7 +222,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
}
break;
case TGPhotoPaintTextEntityStyleFrame:
case TGPhotoPaintTextEntityStyleFramed:
{
CGFloat lightness = 0.0f;
CGFloat r = 0.0f;
@ -601,15 +601,35 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
[super showCGGlyphs:glyphs positions:positions count:glyphCount font:font matrix:textMatrix attributes:attributes inContext:context];
}
- (void)prepare {
_path = nil;
[self.rectArray removeAllObjects];
[self enumerateLineFragmentsForGlyphRange:NSMakeRange(0, self.textStorage.string) usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
bool ignoreRange = false;
NSRange characterRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
NSString *substring = [[self.textStorage string] substringWithRange:characterRange];
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];
}
- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
[super drawBackgroundForGlyphRange:glyphsToShow atPoint: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);
@ -618,22 +638,23 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
CGContextSetFillColorWithColor(context, self.frameColor.CGColor);
CGContextSetStrokeColorWithColor(context, self.frameColor.CGColor);
_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 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];
@ -646,8 +667,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
if (i == 0) {
last = cur;
} 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 b = CGPointMake(CGRectGetMaxX(cur), cur.origin.y);
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)];
[self.path appendPath:addPath];
}
last = cur;
}
}
[self.path fill];

View File

@ -8,9 +8,6 @@
@property (nonatomic, copy) void (^fontChanged)(TGPhotoPaintFont *font);
@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;
@end

View File

@ -8,7 +8,7 @@
#import <LegacyComponents/TGModernButton.h>
#import "TGPhotoTextEntityView.h"
const CGFloat TGPhotoTextSettingsViewMargin = 19.0f;
const CGFloat TGPhotoTextSettingsViewMargin = 10.0f;
const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
@interface TGPhotoTextSettingsView ()
@ -17,11 +17,13 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
UIInterfaceOrientation _interfaceOrientation;
UIImageView *_backgroundView;
UIView *_wrapperView;
UIView *_contentView;
UIVisualEffectView *_effectView;
NSArray *_fontViews;
NSArray *_fontIconViews;
NSArray *_fontSeparatorViews;
UIImageView *_selectedCheckView;
}
@end
@ -38,95 +40,91 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
_interfaceOrientation = UIInterfaceOrientationPortrait;
_backgroundView = [[UIImageView alloc] init];
_backgroundView.alpha = 0.98f;
[self addSubview:_backgroundView];
_wrapperView = [[UIView alloc] init];
_wrapperView.clipsToBounds = true;
_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 *fontIconViews = [[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)];
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)];
TGModernButton *frameButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
frameButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
frameButton.titleLabel.font = font;
frameButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 44.0f, 0.0f, 0.0f);
frameButton.tag = TGPhotoPaintTextEntityStyleFrame;
[frameButton setTitle:@"" forState:UIControlStateNormal];
[frameButton setTitleColor:[UIColor blackColor]];
frameButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 16.0f, 0.0f, 0.0f);
frameButton.tag = TGPhotoPaintTextEntityStyleFramed;
[frameButton setTitle:TGLocalized(@"Paint.Framed") forState:UIControlStateNormal];
[frameButton setTitleColor:[UIColor whiteColor]];
[frameButton addTarget:self action:@selector(styleValueChanged:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:frameButton];
[_contentView addSubview:frameButton];
[fontViews addObject:frameButton];
textView = [[TGPhotoTextView alloc] init];
textView.backgroundColor = [UIColor clearColor];
textView.textColor = [UIColor whiteColor];
textView.frameColor = [UIColor blackColor];
textView.font = font;
textView.text = TGLocalized(@"Paint.Framed");
[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));
[frameButton addSubview:textView];
UIImageView *iconView = [[UIImageView alloc] initWithImage:TGTintedImage([UIImage imageNamed:@"Editor/TextFramed"], [UIColor whiteColor])];
[frameButton addSubview:iconView];
[fontIconViews addObject:iconView];
TGModernButton *outlineButton = [[TGModernButton alloc] initWithFrame:CGRectZero];
outlineButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
outlineButton.titleLabel.font = font;
outlineButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 16.0f, 0.0f, 0.0f);
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;
_fontIconViews = fontIconViews;
_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;
}
- (void)fontButtonPressed:(TGModernButton *)sender
{
[sender addSubview:_selectedCheckView];
if (self.fontChanged != nil)
self.fontChanged(_fonts[sender.tag]);
}
@ -139,28 +137,22 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
- (void)present
{
self.alpha = 0.0f;
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.2 animations:^
[UIView animateWithDuration:0.25 animations:^
{
self.alpha = 1.0f;
_effectView.alpha = 1.0f;
_contentView.alpha = 1.0f;
} completion:^(__unused BOOL finished)
{
self.layer.shouldRasterize = false;
}];
}
- (void)dismissWithCompletion:(void (^)(void))completion
{
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.15 animations:^
[UIView animateWithDuration:0.2 animations:^
{
self.alpha = 0.0f;
_effectView.alpha = 0.0f;
_contentView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
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
{
return CGSizeMake(256, _fontViews.count * TGPhotoTextSettingsItemHeight + TGPhotoTextSettingsViewMargin * 2);
return CGSizeMake(220, _fontViews.count * TGPhotoTextSettingsItemHeight + TGPhotoTextSettingsViewMargin * 2);
}
- (void)setInterfaceOrientation:(UIInterfaceOrientation)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];
}
- (void)layoutSubviews
{
CGFloat arrowSize = 0.0f;
switch (self.interfaceOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupLandscapeLeftBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)];
_backgroundView.frame = CGRectMake(TGPhotoTextSettingsViewMargin - 13.0f, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + 13.0f, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
_wrapperView.frame = CGRectMake(TGPhotoTextSettingsViewMargin - arrowSize, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + arrowSize, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
}
break;
case UIInterfaceOrientationLandscapeRight:
{
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupLandscapeRightBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)];
_backgroundView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + 13.0f, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
_wrapperView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2 + arrowSize, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2);
}
break;
default:
{
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupPortraitBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)];
_backgroundView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2 + 13.0f);
_wrapperView.frame = CGRectMake(TGPhotoTextSettingsViewMargin, TGPhotoTextSettingsViewMargin, self.frame.size.width - TGPhotoTextSettingsViewMargin * 2, self.frame.size.height - TGPhotoTextSettingsViewMargin * 2 + arrowSize);
}
break;
}
@ -251,13 +200,17 @@ const CGFloat TGPhotoTextSettingsItemHeight = 44.0f;
[_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)
{
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];
model.controller = galleryController;
model.stickersContext = stickersContext;
//model.suggestionContext = self.suggestionContext;
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"]) {
UIImage *renderImage = [[UIImage alloc] initWithData:dict[@"data"]];
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.position = [dict[@"position"] CGPointValue];
entity.scale = [dict[@"scale"] floatValue];

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