mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-01 16:06:59 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
db713754e1
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||
|
2
Makefile
2
Makefile
@ -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)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 114;
|
||||
return 115;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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] = []
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_font.pdf",
|
||||
"filename" : "ic_menu_brush4.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushArrow.imageset/ic_menu_brush4.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushArrow.imageset/ic_menu_brush4.pdf
vendored
Normal file
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushMarker.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushMarker.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_brush2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushMarker.imageset/ic_menu_brush2.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushMarker.imageset/ic_menu_brush2.pdf
vendored
Normal file
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushNeon.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushNeon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_brush3.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushNeon.imageset/ic_menu_brush3.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushNeon.imageset/ic_menu_brush3.pdf
vendored
Normal file
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushPen.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushPen.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_brush1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushPen.imageset/ic_menu_brush1.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushPen.imageset/ic_menu_brush1.pdf
vendored
Normal file
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_brush4.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_brush2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_brush3.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/BrushSelectedPen.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_brush1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextFramed.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextFramed.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_font3.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextFramed.imageset/ic_menu_font3.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextFramed.imageset/ic_menu_font3.pdf
vendored
Normal file
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextOutlined.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextOutlined.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_font2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextOutlined.imageset/ic_menu_font2.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextOutlined.imageset/ic_menu_font2.pdf
vendored
Normal file
Binary file not shown.
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextRegular.imageset/Contents.json
vendored
Normal file
12
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextRegular.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_font1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextRegular.imageset/ic_menu_font1.pdf
vendored
Normal file
BIN
submodules/LegacyComponents/LegacyImages.xcassets/Editor/TextRegular.imageset/ic_menu_font1.pdf
vendored
Normal file
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_font3.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_font2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_editor_font1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -4,9 +4,9 @@
|
||||
@class TGPhotoPaintFont;
|
||||
|
||||
typedef enum {
|
||||
TGPhotoPaintTextEntityStyleBorder,
|
||||
TGPhotoPaintTextEntityStyleClassic,
|
||||
TGPhotoPaintTextEntityStyleFrame
|
||||
TGPhotoPaintTextEntityStyleOutlined,
|
||||
TGPhotoPaintTextEntityStyleRegular,
|
||||
TGPhotoPaintTextEntityStyleFramed
|
||||
} TGPhotoPaintTextEntityStyle;
|
||||
|
||||
@interface TGPhotoPaintTextEntity : TGPhotoPaintEntity
|
||||
|
@ -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)
|
||||
|
@ -14,6 +14,7 @@
|
||||
- (void)cancelProcessing;
|
||||
- (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer;
|
||||
|
||||
- (void)process;
|
||||
- (void)reprocessCurrent;
|
||||
|
||||
@end
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
|
||||
- (TGPhotoEditorTab)toolbarTabs
|
||||
{
|
||||
return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab | TGPhotoEditorTimerTab;
|
||||
return TGPhotoEditorCropTab | TGPhotoEditorToolsTab | TGPhotoEditorPaintTab;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -50,9 +50,7 @@
|
||||
return;
|
||||
|
||||
if (next.selected)
|
||||
{
|
||||
[strongSelf addSelectedItem:next.item];
|
||||
}
|
||||
else if (!strongSelf->_keepItems)
|
||||
[strongSelf removeSelectedItem:next.item];
|
||||
}]];
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
5
submodules/LegacyComponents/Sources/TGPaintArrowBrush.h
Normal file
5
submodules/LegacyComponents/Sources/TGPaintArrowBrush.h
Normal file
@ -0,0 +1,5 @@
|
||||
#import "TGPaintBrush.h"
|
||||
|
||||
@interface TGPaintArrowBrush : TGPaintBrush
|
||||
|
||||
@end
|
90
submodules/LegacyComponents/Sources/TGPaintArrowBrush.m
Normal file
90
submodules/LegacyComponents/Sources/TGPaintArrowBrush.m
Normal 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
|
@ -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;
|
||||
|
@ -61,6 +61,11 @@ const CGSize TGPaintBrushPreviewTextureSize = { 64.0f, 64.0f };
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
- (CGFloat)dynamic
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
- (bool)lightSaber
|
||||
{
|
||||
return false;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
});
|
||||
}];
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
- (void)updateVisibility:(bool)visible;
|
||||
|
||||
- (UIColor *)colorAtPoint:(CGPoint)point;
|
||||
|
||||
- (void)setupWithPaintingData:(TGPaintingData *)paintingData;
|
||||
- (TGPhotoPaintEntityView *)createEntityViewWithEntity:(TGPhotoPaintEntity *)entity;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
157
submodules/LegacyComponents/Sources/TGPhotoPaintEyedropperView.m
Normal file
157
submodules/LegacyComponents/Sources/TGPhotoPaintEyedropperView.m
Normal 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
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user