Cherry-pick various fixes
@ -5867,6 +5867,7 @@ Sorry for the inconvenience.";
|
||||
"Chat.PinnedMessagesHiddenText" = "You will see the bar with pinned messages only if a new message is pinned.";
|
||||
|
||||
"OpenFile.PotentiallyDangerousContentAlert" = "Previewing this file can potentially expose your IP address to its sender. Continue?";
|
||||
"OpenFile.Proceed" = "Proceed";
|
||||
|
||||
"Chat.PinnedListPreview.ShowAllMessages" = "Show All Messages";
|
||||
"Chat.PinnedListPreview.UnpinAllMessages" = "Unpin All Messages";
|
||||
@ -5887,3 +5888,5 @@ Sorry for the inconvenience.";
|
||||
"Stats.Message.Views" = "Views";
|
||||
"Stats.Message.PublicShares" = "Public Shares";
|
||||
"Stats.Message.PrivateShares" = "Private Shares";
|
||||
|
||||
"Conversation.TextCopied" = "Text copied to clipboard";
|
||||
|
@ -18,8 +18,9 @@ public final class GalleryControllerActionInteraction {
|
||||
public let openBotCommand: (String) -> Void
|
||||
public let addContact: (String) -> Void
|
||||
public let storeMediaPlaybackState: (MessageId, Double?) -> Void
|
||||
public let editMedia: (MessageId) -> Void
|
||||
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void) {
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, editMedia: @escaping (MessageId) -> Void) {
|
||||
self.openUrl = openUrl
|
||||
self.openUrlIn = openUrlIn
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -28,5 +29,6 @@ public final class GalleryControllerActionInteraction {
|
||||
self.openBotCommand = openBotCommand
|
||||
self.addContact = addContact
|
||||
self.storeMediaPlaybackState = storeMediaPlaybackState
|
||||
self.editMedia = editMedia
|
||||
}
|
||||
}
|
||||
|
@ -264,6 +264,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
|
||||
private(set) var emptyNode: ChatListEmptyNode?
|
||||
var emptyShimmerEffectNode: ChatListShimmerNode?
|
||||
private var shimmerNodeOffset: CGFloat = 0.0
|
||||
let listNode: ChatListNode
|
||||
|
||||
private var validLayout: (CGSize, UIEdgeInsets, CGFloat)?
|
||||
@ -285,8 +286,12 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
var needsShimmerNode = false
|
||||
var shimmerNodeOffset: CGFloat = 0.0
|
||||
switch isEmptyState {
|
||||
case let .empty(isLoading):
|
||||
case let .empty(isLoading, hasArchiveInfo):
|
||||
if hasArchiveInfo {
|
||||
shimmerNodeOffset = 253.0
|
||||
}
|
||||
if isLoading {
|
||||
needsShimmerNode = true
|
||||
|
||||
@ -326,12 +331,13 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if needsShimmerNode {
|
||||
strongSelf.shimmerNodeOffset = shimmerNodeOffset
|
||||
if strongSelf.emptyShimmerEffectNode == nil {
|
||||
let emptyShimmerEffectNode = ChatListShimmerNode()
|
||||
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
|
||||
strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode)
|
||||
if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: .immediate)
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
} else if let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||
@ -349,7 +355,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
}
|
||||
strongSelf.floatingHeaderOffset = offset
|
||||
if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: transition)
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,25 +226,23 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.paneContainerNode.interaction = interaction
|
||||
|
||||
self.paneContainerNode.currentPaneUpdated = { [weak self] key, transitionFraction, transition in
|
||||
if let strongSelf = self {
|
||||
var filterKey: ChatListSearchFilter?
|
||||
if let key = key {
|
||||
switch key {
|
||||
case .chats:
|
||||
filterKey = .chats
|
||||
case .media:
|
||||
filterKey = .media
|
||||
case .links:
|
||||
filterKey = .links
|
||||
case .files:
|
||||
filterKey = .files
|
||||
case .music:
|
||||
filterKey = .music
|
||||
case .voice:
|
||||
filterKey = .voice
|
||||
}
|
||||
if let strongSelf = self, let key = key {
|
||||
var filterKey: ChatListSearchFilter
|
||||
switch key {
|
||||
case .chats:
|
||||
filterKey = .chats
|
||||
case .media:
|
||||
filterKey = .media
|
||||
case .links:
|
||||
filterKey = .links
|
||||
case .files:
|
||||
filterKey = .files
|
||||
case .music:
|
||||
filterKey = .music
|
||||
case .voice:
|
||||
filterKey = .voice
|
||||
}
|
||||
strongSelf.selectedFilterKey = filterKey.flatMap { .filter($0.id) }
|
||||
strongSelf.selectedFilterKey = .filter(filterKey.id)
|
||||
strongSelf.selectedFilterKeyPromise.set(.single(strongSelf.selectedFilterKey))
|
||||
strongSelf.transitionFraction = transitionFraction
|
||||
|
||||
@ -306,16 +304,19 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
|
||||
let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|
||||
|> take(1)
|
||||
|
||||
self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterKeyPromise.get(), self.searchQuery.get())
|
||||
|> mapToSignal { peers, dates, selectedFilter, searchQuery -> Signal<([Peer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?), NoError> in
|
||||
self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterKeyPromise.get(), self.searchQuery.get(), accountPeer)
|
||||
|> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([Peer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, Peer?), NoError> in
|
||||
if searchQuery?.isEmpty ?? true {
|
||||
return .single((peers, dates, selectedFilter))
|
||||
return .single((peers, dates, selectedFilter, searchQuery, accountPeer))
|
||||
} else {
|
||||
return (.complete() |> delay(0.25, queue: Queue.mainQueue()))
|
||||
|> then(.single((peers, dates, selectedFilter)))
|
||||
|> then(.single((peers, dates, selectedFilter, searchQuery, accountPeer)))
|
||||
}
|
||||
} |> map { peers, dates, selectedFilter -> [ChatListSearchFilter] in
|
||||
} |> map { peers, dates, selectedFilter, searchQuery, accountPeer -> [ChatListSearchFilter] in
|
||||
var suggestedFilters: [ChatListSearchFilter] = []
|
||||
if !dates.isEmpty {
|
||||
let formatter = DateFormatter()
|
||||
@ -327,8 +328,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
suggestedFilters.append(.date(minDate.flatMap { Int32($0.timeIntervalSince1970) }, Int32(maxDate.timeIntervalSince1970), title))
|
||||
}
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var existingPeerIds = Set<PeerId>()
|
||||
var peers = peers
|
||||
if let accountPeer = accountPeer, let lowercasedQuery = searchQuery?.lowercased(), lowercasedQuery.count > 1 && (presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery)) {
|
||||
peers.insert(accountPeer, at: 0)
|
||||
}
|
||||
|
||||
if !peers.isEmpty && selectedFilter != .filter(ChatListSearchFilter.chats.id) {
|
||||
for peer in peers {
|
||||
if existingPeerIds.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
let isGroup: Bool
|
||||
if peer.id.namespace == Namespaces.Peer.SecretChat {
|
||||
continue
|
||||
@ -339,8 +350,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
} else {
|
||||
isGroup = false
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
suggestedFilters.append(.peer(peer.id, isGroup, peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.compactDisplayTitle))
|
||||
|
||||
var title: String = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
var compactDisplayTitle = peer.compactDisplayTitle
|
||||
if peer.id == accountPeer?.id {
|
||||
title = presentationData.strings.DialogList_SavedMessages
|
||||
compactDisplayTitle = title
|
||||
}
|
||||
suggestedFilters.append(.peer(peer.id, isGroup, title, compactDisplayTitle))
|
||||
existingPeerIds.insert(peer.id)
|
||||
}
|
||||
}
|
||||
return suggestedFilters
|
||||
|
@ -403,7 +403,7 @@ public enum ChatListNodeScrollPosition {
|
||||
|
||||
public enum ChatListNodeEmptyState: Equatable {
|
||||
case notEmpty(containsChats: Bool)
|
||||
case empty(isLoading: Bool)
|
||||
case empty(isLoading: Bool, hasArchiveInfo: Bool)
|
||||
}
|
||||
|
||||
public final class ChatListNode: ListView {
|
||||
@ -1411,6 +1411,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
var isEmpty = false
|
||||
var isLoading = false
|
||||
var hasArchiveInfo = false
|
||||
if transition.chatListView.filteredEntries.isEmpty {
|
||||
isEmpty = true
|
||||
} else {
|
||||
@ -1421,6 +1422,9 @@ public final class ChatListNode: ListView {
|
||||
case .GroupReferenceEntry, .HeaderEntry, .HoleEntry:
|
||||
break
|
||||
default:
|
||||
if case .ArchiveIntro = entry {
|
||||
hasArchiveInfo = true
|
||||
}
|
||||
isEmpty = false
|
||||
break loop1
|
||||
}
|
||||
@ -1441,14 +1445,21 @@ public final class ChatListNode: ListView {
|
||||
if !hasHoles {
|
||||
isLoading = false
|
||||
}
|
||||
} else {
|
||||
for entry in transition.chatListView.filteredEntries.reversed().prefix(2) {
|
||||
if case .ArchiveIntro = entry {
|
||||
hasArchiveInfo = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isEmptyState: ChatListNodeEmptyState
|
||||
if transition.chatListView.isLoading {
|
||||
isEmptyState = .empty(isLoading: true)
|
||||
isEmptyState = .empty(isLoading: true, hasArchiveInfo: hasArchiveInfo)
|
||||
} else if isEmpty {
|
||||
isEmptyState = .empty(isLoading: isLoading)
|
||||
isEmptyState = .empty(isLoading: isLoading, hasArchiveInfo: false)
|
||||
} else {
|
||||
var containsChats = false
|
||||
loop: for entry in transition.chatListView.filteredEntries {
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
bool TGIsGzippedData(NSData *data) {
|
||||
const UInt8 *bytes = (const UInt8 *)data.bytes;
|
||||
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
|
||||
return data.length >= 2 && ((bytes[0] == 0x1f && bytes[1] == 0x8b) || (bytes[0] == 0x78 && bytes[1] == 0x9c));
|
||||
}
|
||||
|
||||
NSData *TGGZipData(NSData *data, float level) {
|
||||
|
@ -21,6 +21,7 @@ import TextSelectionNode
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
||||
private let editImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Draw"), color: .white)
|
||||
|
||||
private let backwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/BackwardButton"), color: .white)
|
||||
private let forwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ForwardButton"), color: .white)
|
||||
@ -47,7 +48,7 @@ private let dateFont = Font.regular(14.0)
|
||||
|
||||
enum ChatItemGalleryFooterContent: Equatable {
|
||||
case info
|
||||
case fetch(status: MediaResourceStatus)
|
||||
case fetch(status: MediaResourceStatus, seekable: Bool)
|
||||
case playback(paused: Bool, seekable: Bool)
|
||||
|
||||
static func ==(lhs: ChatItemGalleryFooterContent, rhs: ChatItemGalleryFooterContent) -> Bool {
|
||||
@ -58,8 +59,8 @@ enum ChatItemGalleryFooterContent: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .fetch(lhsStatus):
|
||||
if case let .fetch(rhsStatus) = rhs, lhsStatus == rhsStatus {
|
||||
case let .fetch(lhsStatus, lhsSeekable):
|
||||
if case let .fetch(rhsStatus, rhsSeekable) = rhs, lhsStatus == rhsStatus, lhsSeekable == rhsSeekable {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -116,6 +117,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
private let contentNode: ASDisplayNode
|
||||
private let deleteButton: UIButton
|
||||
private let actionButton: UIButton
|
||||
private let editButton: UIButton
|
||||
private let maskNode: ASDisplayNode
|
||||
private let scrollWrapperNode: CaptionScrollWrapperNode
|
||||
private let scrollNode: ASScrollNode
|
||||
@ -165,11 +167,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.playbackControlButton.isHidden = true
|
||||
self.statusButtonNode.isHidden = true
|
||||
self.statusNode.isHidden = true
|
||||
case let .fetch(status):
|
||||
case let .fetch(status, seekable):
|
||||
self.authorNameNode.isHidden = true
|
||||
self.dateNode.isHidden = true
|
||||
self.backwardButton.isHidden = true
|
||||
self.forwardButton.isHidden = true
|
||||
self.backwardButton.isHidden = !seekable
|
||||
self.forwardButton.isHidden = !seekable
|
||||
if status == .Local {
|
||||
self.playbackControlButton.isHidden = false
|
||||
self.playbackControlButton.setImage(playImage, for: [])
|
||||
@ -217,8 +219,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
}
|
||||
didSet {
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.setCollapsed(self.visibilityAlpha < 1.0 ? true : false, animated: false)
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.setCollapsed(self.visibilityAlpha < 1.0, animated: false)
|
||||
self.view.addSubview(scrubberView)
|
||||
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
@ -253,7 +255,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
override func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.contentNode.alpha = alpha
|
||||
self.scrubberView?.setCollapsed(alpha < 1.0 ? true : false, animated: animated)
|
||||
self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated)
|
||||
}
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) {
|
||||
@ -268,9 +270,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.deleteButton = UIButton()
|
||||
self.actionButton = UIButton()
|
||||
self.editButton = UIButton()
|
||||
|
||||
self.deleteButton.setImage(deleteImage, for: [.normal])
|
||||
self.actionButton.setImage(actionImage, for: [.normal])
|
||||
self.editButton.setImage(editImage, for: [.normal])
|
||||
|
||||
self.scrollWrapperNode = CaptionScrollWrapperNode()
|
||||
self.scrollWrapperNode.clipsToBounds = true
|
||||
@ -340,6 +344,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.contentNode.view.addSubview(self.deleteButton)
|
||||
self.contentNode.view.addSubview(self.actionButton)
|
||||
// self.contentNode.view.addSubview(self.editButton)
|
||||
self.contentNode.addSubnode(self.scrollWrapperNode)
|
||||
self.scrollWrapperNode.addSubnode(self.scrollNode)
|
||||
self.scrollNode.addSubnode(self.textNode)
|
||||
@ -356,6 +361,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
|
||||
self.editButton.addTarget(self, action: #selector(self.editButtonPressed), for: [.touchUpInside])
|
||||
|
||||
self.backwardButton.addTarget(self, action: #selector(self.backwardButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
|
||||
@ -436,6 +442,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
if origin == nil {
|
||||
self.deleteButton.isHidden = true
|
||||
self.editButton.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,6 +451,16 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
let canDelete: Bool
|
||||
var canShare = !message.containsSecretMedia && !Namespaces.Message.allScheduled.contains(message.id.namespace)
|
||||
|
||||
var canEdit = false
|
||||
for media in message.media {
|
||||
if media is TelegramMediaImage {
|
||||
canEdit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
canEdit = canEdit && !message.containsSecretMedia
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||
canDelete = true
|
||||
@ -452,17 +469,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if message.flags.contains(.Incoming) {
|
||||
canDelete = channel.hasPermission(.deleteAllMessages)
|
||||
canEdit = canEdit && channel.hasPermission(.editAllMessages)
|
||||
} else {
|
||||
canDelete = true
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
canEdit = false
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
canShare = false
|
||||
canEdit = false
|
||||
}
|
||||
|
||||
|
||||
var authorNameText: String?
|
||||
if let author = message.effectiveAuthor {
|
||||
authorNameText = author.displayTitle(strings: self.strings, displayOrder: self.nameOrder)
|
||||
@ -496,10 +517,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
messageText = galleryCaptionStringWithAppliedEntities(message.text, entities: entities)
|
||||
}
|
||||
|
||||
self.editButton.isHidden = message.containsSecretMedia
|
||||
|
||||
self.actionButton.isHidden = message.containsSecretMedia || Namespaces.Message.allScheduled.contains(message.id.namespace)
|
||||
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
self.currentMessageText = messageText
|
||||
|
||||
if messageText.length == 0 {
|
||||
@ -519,6 +540,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
self.deleteButton.isHidden = !canDelete
|
||||
self.actionButton.isHidden = !canShare
|
||||
self.editButton.isHidden = !canEdit
|
||||
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
@ -620,20 +642,22 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
if self.textNode.isHidden || !displayCaption {
|
||||
panelHeight += 8.0
|
||||
} else {
|
||||
scrubberY = panelHeight - bottomInset - 44.0 - 41.0
|
||||
scrubberY = panelHeight - bottomInset - 44.0 - 44.0
|
||||
if contentInset > 0.0 {
|
||||
scrubberY -= contentInset + 3.0
|
||||
scrubberY -= contentInset
|
||||
}
|
||||
}
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: leftInset, y: scrubberY), size: CGSize(width: width - leftInset - rightInset, height: 34.0))
|
||||
scrubberView.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
transition.updateFrame(layer: scrubberView.layer, frame: scrubberFrame)
|
||||
transition.updateBounds(layer: scrubberView.layer, bounds: CGRect(origin: CGPoint(), size: scrubberFrame.size))
|
||||
transition.updatePosition(layer: scrubberView.layer, position: CGPoint(x: scrubberFrame.midX, y: scrubberFrame.midY))
|
||||
}
|
||||
transition.updateAlpha(node: self.textNode, alpha: displayCaption ? 1.0 : 0.0)
|
||||
|
||||
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.editButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - 50.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
if let image = self.backwardButton.image(for: .normal) {
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
@ -708,6 +732,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.authorNameNode.alpha = 1.0
|
||||
self.deleteButton.alpha = 1.0
|
||||
self.actionButton.alpha = 1.0
|
||||
self.editButton.alpha = 1.0
|
||||
self.backwardButton.alpha = 1.0
|
||||
self.forwardButton.alpha = 1.0
|
||||
self.statusNode.alpha = 1.0
|
||||
@ -730,6 +755,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.authorNameNode.alpha = 0.0
|
||||
self.deleteButton.alpha = 0.0
|
||||
self.actionButton.alpha = 0.0
|
||||
self.editButton.alpha = 0.0
|
||||
self.backwardButton.alpha = 0.0
|
||||
self.forwardButton.alpha = 0.0
|
||||
self.statusNode.alpha = 0.0
|
||||
@ -1049,6 +1075,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
}
|
||||
|
||||
@objc func editButtonPressed() {
|
||||
guard let message = self.currentMessage else {
|
||||
return
|
||||
}
|
||||
self.controllerInteraction?.editMedia(message.id)
|
||||
}
|
||||
|
||||
@objc func playbackControlPressed() {
|
||||
self.playbackControl?()
|
||||
}
|
||||
|
@ -441,9 +441,9 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
}
|
||||
} else {
|
||||
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false))
|
||||
@ -911,6 +911,11 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { [weak self] messageId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss(forceAway: true)
|
||||
strongSelf.actionInteraction?.editMedia(messageId)
|
||||
}
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -3,16 +3,19 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
public final class GalleryControllerInteraction {
|
||||
public let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
|
||||
public let dismissController: () -> Void
|
||||
public let replaceRootController: (ViewController, Promise<Bool>?) -> Void
|
||||
public let editMedia: (MessageId) -> Void
|
||||
|
||||
public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void) {
|
||||
public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, editMedia: @escaping (MessageId) -> Void) {
|
||||
self.presentController = presentController
|
||||
self.dismissController = dismissController
|
||||
self.replaceRootController = replaceRootController
|
||||
self.editMedia = editMedia
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,7 +517,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
|
||||
}
|
||||
if self.itemNodes.isEmpty {
|
||||
let node = self.makeNodeForItem(at: self.centralItemIndex ?? 0, synchronous: synchronous)
|
||||
node.frame = CGRect(origin: CGPoint(), size: scrollView.bounds.size)
|
||||
node.frame = CGRect(origin: CGPoint(), size: self.scrollView.bounds.size)
|
||||
if let containerLayout = self.containerLayout {
|
||||
node.containerLayoutUpdated(containerLayout.0, navigationBarHeight: containerLayout.1, transition: .immediate)
|
||||
}
|
||||
|
@ -125,9 +125,15 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDel
|
||||
self.items = items
|
||||
self.itemNodes = itemNodes
|
||||
}
|
||||
|
||||
var updatedIndexOnly = false
|
||||
if let centralIndexAndProgress = self.centralIndexAndProgress, centralIndexAndProgress.0 != centralIndex, centralIndexAndProgress.1 == progress {
|
||||
updatedIndexOnly = true
|
||||
}
|
||||
|
||||
self.centralIndexAndProgress = (centralIndex, progress)
|
||||
if let size = self.currentLayout {
|
||||
self.updateLayout(size: size, transition: .immediate)
|
||||
self.updateLayout(size: size, transition: updatedIndexOnly ? .animated(duration: 0.2, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,8 +449,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if let (previousLayout, _) = self.validLayout, self.dismissOnOrientationChange, previousLayout.size.width > previousLayout.size.height && previousLayout.size.height == layout.size.width {
|
||||
dismiss = true
|
||||
}
|
||||
let hadLayout = self.validLayout != nil
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
|
||||
if !hadLayout {
|
||||
self.zoomableContent = zoomableContent
|
||||
}
|
||||
|
||||
let statusDiameter: CGFloat = 50.0
|
||||
let statusFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusDiameter) / 2.0), y: floor((layout.size.height - statusDiameter) / 2.0)), size: CGSize(width: statusDiameter, height: statusDiameter))
|
||||
transition.updateFrame(node: self.statusButtonNode, frame: statusFrame)
|
||||
@ -530,7 +535,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
let mediaManager = item.context.sharedContext.mediaManager
|
||||
|
||||
let videoNode = UniversalVideoNode(postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery)
|
||||
|
||||
let videoScale: CGFloat
|
||||
if item.content is WebEmbedVideoContent {
|
||||
videoScale = 1.0
|
||||
@ -578,6 +582,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
self.requiresDownload = true
|
||||
var mediaFileStatus: Signal<MediaResourceStatus?, NoError> = .single(nil)
|
||||
|
||||
var hintSeekable = false
|
||||
if let contentInfo = item.contentInfo, case let .message(message) = contentInfo {
|
||||
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
|
||||
disablePictureInPicture = true
|
||||
@ -611,6 +617,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
if let file = file {
|
||||
for attribute in file.attributes {
|
||||
if case let .Video(duration, _, _) = attribute, duration >= 30 {
|
||||
hintSeekable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
let status = messageMediaFileStatus(context: item.context, messageId: message.id, file: file)
|
||||
if !isWebpage {
|
||||
self.scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size)
|
||||
@ -634,7 +646,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
var initialBuffering = false
|
||||
var playing = false
|
||||
var isPaused = true
|
||||
var seekable = false
|
||||
var seekable = hintSeekable
|
||||
var hasStarted = false
|
||||
var displayProgress = true
|
||||
if let value = value {
|
||||
@ -682,13 +694,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if !content.enableSound {
|
||||
isPaused = false
|
||||
}
|
||||
} else {
|
||||
} else if strongSelf.actionAtEnd == .stop {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
strongSelf.controlsTimer?.invalidate()
|
||||
strongSelf.controlsTimer = nil
|
||||
}
|
||||
}
|
||||
seekable = value.duration >= 30.0
|
||||
if !value.duration.isZero {
|
||||
seekable = value.duration >= 30.0
|
||||
}
|
||||
}
|
||||
|
||||
if strongSelf.isCentral && playing && strongSelf.previousPlaying != true && !disablePlayerControls {
|
||||
@ -749,7 +763,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if hasStarted || strongSelf.didPause {
|
||||
strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable)
|
||||
} else if let fetchStatus = fetchStatus, !strongSelf.requiresDownload {
|
||||
strongSelf.footerContentNode.content = .fetch(status: fetchStatus)
|
||||
strongSelf.footerContentNode.content = .fetch(status: fetchStatus, seekable: seekable)
|
||||
}
|
||||
} else {
|
||||
strongSelf.footerContentNode.content = .playback(paused: false, seekable: seekable)
|
||||
@ -773,11 +787,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
|
||||
videoNode.playbackCompleted = { [weak videoNode] in
|
||||
videoNode.playbackCompleted = { [weak self, weak videoNode] in
|
||||
Queue.mainQueue().async {
|
||||
item.playbackCompleted()
|
||||
if !isAnimated {
|
||||
if let strongSelf = self, !isAnimated {
|
||||
videoNode?.seek(0.0)
|
||||
|
||||
if strongSelf.actionAtEnd == .stop {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
strongSelf.controlsTimer?.invalidate()
|
||||
strongSelf.controlsTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +208,7 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { _, _ in
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = SecretMediaPreviewControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -1228,7 +1228,15 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if let map = media.media as? TelegramMediaMap {
|
||||
let controller = legacyLocationController(message: nil, mapMedia: map, context: self.context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: { }, openUrl: { _ in })
|
||||
let controllerParams = LocationViewParams(sendLiveLocation: { _ in
|
||||
}, stopLiveLocation: { _ in
|
||||
}, openUrl: { _ in }, openPeer: { _ in
|
||||
}, showAll: false)
|
||||
|
||||
let peer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer, text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||
|
||||
let controller = LocationViewController(context: self.context, subject: message, params: controllerParams)
|
||||
self.pushController(controller)
|
||||
return
|
||||
}
|
||||
|
@ -358,12 +358,13 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, dismissController: { [weak self] in
|
||||
self?.dismiss(forceAway: true)
|
||||
}, replaceRootController: { [weak self] controller, ready in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replaceRootController(controller, ready)
|
||||
}
|
||||
}, editMedia: { _ in
|
||||
})
|
||||
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||
self.displayNodeDidLoad()
|
||||
|
@ -753,8 +753,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
||||
let cfRunRange = CTRunGetStringRange(run)
|
||||
let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length)
|
||||
string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in
|
||||
if let id = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaIdAttribute)] as? Int64, let dimensions = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaDimensionsAttribute)] as? CGSize {
|
||||
var imageFrame = CGRect(origin: CGPoint(), size: dimensions)
|
||||
if let id = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaIdAttribute)] as? Int64, let dimensions = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaDimensionsAttribute)] as? PixelDimensions {
|
||||
var imageFrame = CGRect(origin: CGPoint(), size: dimensions.cgSize)
|
||||
|
||||
let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
|
||||
let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels((fontLineHeight - imageFrame.size.height) / 2.0)
|
||||
|
@ -457,9 +457,9 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
if let editableControlSizeAndApply = editableControlSizeAndApply {
|
||||
let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset, y: 0.0), size: CGSize(width: editableControlSizeAndApply.0, height: layout.size.height))
|
||||
let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset, y: 0.0), size: CGSize(width: editableControlSizeAndApply.0, height: layout.contentSize.height))
|
||||
if strongSelf.editableControlNode == nil {
|
||||
let editableControlNode = editableControlSizeAndApply.1(layout.size.height)
|
||||
let editableControlNode = editableControlSizeAndApply.1(layout.contentSize.height)
|
||||
editableControlNode.tapped = {
|
||||
if let strongSelf = self {
|
||||
strongSelf.setRevealOptionsOpened(true, animated: true)
|
||||
|
@ -136,15 +136,7 @@
|
||||
#import <LegacyComponents/TGLiveUploadInterface.h>
|
||||
#import <LegacyComponents/TGLocalMessageMetaMediaAttachment.h>
|
||||
#import <LegacyComponents/TGLocalization.h>
|
||||
#import <LegacyComponents/TGLocationLiveElapsedView.h>
|
||||
#import <LegacyComponents/TGLocationLiveSessionItemView.h>
|
||||
#import <LegacyComponents/TGLocationMapViewController.h>
|
||||
#import <LegacyComponents/TGLocationMediaAttachment.h>
|
||||
#import <LegacyComponents/TGLocationPickerController.h>
|
||||
#import <LegacyComponents/TGLocationPulseView.h>
|
||||
#import <LegacyComponents/TGLocationVenue.h>
|
||||
#import <LegacyComponents/TGLocationViewController.h>
|
||||
#import <LegacyComponents/TGLocationWavesView.h>
|
||||
#import <LegacyComponents/TGMediaAsset.h>
|
||||
#import <LegacyComponents/TGMediaAssetFetchResult.h>
|
||||
#import <LegacyComponents/TGMediaAssetFetchResultChange.h>
|
||||
@ -267,8 +259,6 @@
|
||||
#import <LegacyComponents/TGRemoteImageView.h>
|
||||
#import <LegacyComponents/TGReplyMarkupAttachment.h>
|
||||
#import <LegacyComponents/TGReplyMessageMediaAttachment.h>
|
||||
#import <LegacyComponents/TGSearchBar.h>
|
||||
#import <LegacyComponents/TGSearchDisplayMixin.h>
|
||||
#import <LegacyComponents/TGStaticBackdropAreaData.h>
|
||||
#import <LegacyComponents/TGStaticBackdropImageData.h>
|
||||
#import <LegacyComponents/TGStickerAssociation.h>
|
||||
@ -294,7 +284,6 @@
|
||||
#import <LegacyComponents/TGWebPageMediaAttachment.h>
|
||||
#import <LegacyComponents/UICollectionView+Utils.h>
|
||||
#import <LegacyComponents/UIControl+HitTestEdgeInsets.h>
|
||||
#import <LegacyComponents/UIDevice+PlatformInfo.h>
|
||||
#import <LegacyComponents/UIImage+TG.h>
|
||||
#import <LegacyComponents/UIImage+TGMediaEditableItem.h>
|
||||
#import <LegacyComponents/UIScrollView+TGHacks.h>
|
||||
|
@ -58,10 +58,6 @@ typedef enum {
|
||||
|
||||
- (id<LegacyComponentsAccessChecker>)accessChecker;
|
||||
|
||||
- (SSignal *)stickerPacksSignal;
|
||||
- (SSignal *)maskStickerPacksSignal;
|
||||
- (SSignal *)recentStickerMasksSignal;
|
||||
|
||||
- (id<SDisposable>)requestAudioSession:(TGAudioSessionType)type interrupted:(void (^)())interrupted;
|
||||
|
||||
- (SThreadPool *)sharedMediaImageProcessingThreadPool;
|
||||
@ -71,11 +67,6 @@ typedef enum {
|
||||
- (NSString *)localDocumentDirectoryForLocalDocumentId:(int64_t)localDocumentId version:(int32_t)version;
|
||||
- (NSString *)localDocumentDirectoryForDocumentId:(int64_t)documentId version:(int32_t)version;
|
||||
|
||||
- (SSignal *)jsonForHttpLocation:(NSString *)httpLocation;
|
||||
- (SSignal *)dataForHttpLocation:(NSString *)httpLocation;
|
||||
|
||||
- (NSOperation<LegacyHTTPRequestOperation> *)makeHTTPRequestOperationWithRequest:(NSURLRequest *)request;
|
||||
|
||||
- (void)pausePictureInPicturePlayback;
|
||||
- (void)resumePictureInPicturePlayback;
|
||||
- (void)maybeReleaseVolumeOverlay;
|
||||
|
@ -1,8 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGLocationLiveElapsedView : UIView
|
||||
|
||||
- (void)setColor:(UIColor *)color;
|
||||
- (void)setRemaining:(int32_t)remaining period:(int32_t)period;
|
||||
|
||||
@end
|
@ -1,12 +0,0 @@
|
||||
#import <LegacyComponents/LegacyComponents.h>
|
||||
#import <LegacyComponents/TGMenuSheetButtonItemView.h>
|
||||
#import <SSignalKit/SSignalKit.h>
|
||||
|
||||
@class TGUser;
|
||||
@class TGMessage;
|
||||
|
||||
@interface TGLocationLiveSessionItemView : TGMenuSheetButtonItemView
|
||||
|
||||
- (instancetype)initWithMessage:(TGMessage *)message peer:(id)peer remaining:(SSignal *)remaining action:(void (^)(void))action;
|
||||
|
||||
@end
|
@ -1,84 +0,0 @@
|
||||
#import <LegacyComponents/TGViewController.h>
|
||||
#import <LegacyComponents/LegacyComponentsContext.h>
|
||||
|
||||
#import <MapKit/MapKit.h>
|
||||
|
||||
#import <SSignalKit/SSignalKit.h>
|
||||
|
||||
@class TGLocationMapView;
|
||||
@class TGLocationOptionsView;
|
||||
|
||||
@class TGSearchBarPallete;
|
||||
|
||||
@interface TGLocationPallete : NSObject
|
||||
|
||||
@property (nonatomic, readonly) UIColor *backgroundColor;
|
||||
@property (nonatomic, readonly) UIColor *selectionColor;
|
||||
@property (nonatomic, readonly) UIColor *separatorColor;
|
||||
@property (nonatomic, readonly) UIColor *textColor;
|
||||
@property (nonatomic, readonly) UIColor *secondaryTextColor;
|
||||
@property (nonatomic, readonly) UIColor *accentColor;
|
||||
@property (nonatomic, readonly) UIColor *destructiveColor;
|
||||
@property (nonatomic, readonly) UIColor *locationColor;
|
||||
@property (nonatomic, readonly) UIColor *liveLocationColor;
|
||||
@property (nonatomic, readonly) UIColor *iconColor;
|
||||
@property (nonatomic, readonly) UIColor *sectionHeaderBackgroundColor;
|
||||
@property (nonatomic, readonly) UIColor *sectionHeaderTextColor;
|
||||
@property (nonatomic, readonly) TGSearchBarPallete *searchBarPallete;
|
||||
@property (nonatomic, readonly) UIImage *avatarPlaceholder;
|
||||
|
||||
+ (instancetype)palleteWithBackgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor locationColor:(UIColor *)locationColor liveLocationColor:(UIColor *)liveLocationColor iconColor:(UIColor *)iconColor sectionHeaderBackgroundColor:(UIColor *)sectionHeaderBackgroundColor sectionHeaderTextColor:(UIColor *)sectionHeaderTextColor searchBarPallete:(TGSearchBarPallete *)searchBarPallete avatarPlaceholder:(UIImage *)avatarPlaceholder;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGLocationMapViewController : TGViewController <UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, MKMapViewDelegate>
|
||||
{
|
||||
CLLocationManager *_locationManager;
|
||||
bool _locationServicesDisabled;
|
||||
|
||||
CGFloat _tableViewTopInset;
|
||||
CGFloat _tableViewBottomInset;
|
||||
UITableView *_tableView;
|
||||
UIActivityIndicatorView *_activityIndicator;
|
||||
UILabel *_messageLabel;
|
||||
|
||||
UIView *_mapViewWrapper;
|
||||
TGLocationMapView *_mapView;
|
||||
TGLocationOptionsView *_optionsView;
|
||||
UIImageView *_edgeView;
|
||||
UIImageView *_edgeHighlightView;
|
||||
}
|
||||
|
||||
@property (nonatomic, copy) void (^liveLocationStarted)(CLLocationCoordinate2D coordinate, int32_t period);
|
||||
@property (nonatomic, copy) void (^liveLocationStopped)(void);
|
||||
|
||||
@property (nonatomic, strong) TGLocationPallete *pallete;
|
||||
@property (nonatomic, readonly, strong) UIView *locationMapView;
|
||||
|
||||
- (void)userLocationButtonPressed;
|
||||
|
||||
- (void)setMapCenterCoordinate:(CLLocationCoordinate2D)coordinate offset:(CGPoint)offset animated:(bool)animated;
|
||||
- (void)setMapCenterCoordinate:(CLLocationCoordinate2D)coordinate span:(MKCoordinateSpan)span offset:(CGPoint)offset animated:(bool)animated;
|
||||
|
||||
- (void)updateInsets;
|
||||
- (void)updateMapHeightAnimated:(bool)animated;
|
||||
|
||||
- (CGFloat)visibleContentHeight;
|
||||
- (CGFloat)mapHeight;
|
||||
- (CGFloat)safeAreaInsetBottom;
|
||||
|
||||
- (bool)hasUserLocation;
|
||||
- (SSignal *)userLocationSignal;
|
||||
- (bool)locationServicesDisabled;
|
||||
- (void)updateLocationAvailability;
|
||||
|
||||
@property (nonatomic, strong) id receivingPeer;
|
||||
- (void)_presentLiveLocationMenu:(CLLocationCoordinate2D)coordinate dismissOnCompletion:(bool)dismissOnCompletion;
|
||||
- (CGRect)_liveLocationMenuSourceRect;
|
||||
- (void)_willStartOwnLiveLocation;
|
||||
|
||||
@end
|
||||
|
||||
extern const CGFloat TGLocationMapInset;
|
||||
extern const CGFloat TGLocationMapClipHeight;
|
||||
extern const MKCoordinateSpan TGLocationDefaultSpan;
|
@ -1,29 +0,0 @@
|
||||
#import <LegacyComponents/TGLocationMapViewController.h>
|
||||
#import <LegacyComponents/LegacyComponentsContext.h>
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
@class TGVenueAttachment;
|
||||
@class TGUser;
|
||||
@class TGMessage;
|
||||
|
||||
typedef enum {
|
||||
TGLocationPickerControllerDefaultIntent,
|
||||
TGLocationPickerControllerCustomLocationIntent
|
||||
} TGLocationPickerControllerIntent;
|
||||
|
||||
@interface TGLocationPickerController : TGLocationMapViewController
|
||||
|
||||
@property (nonatomic, copy) void (^locationPicked)(CLLocationCoordinate2D coordinate, TGVenueAttachment *venue, NSString *address);
|
||||
|
||||
@property (nonatomic, copy) SSignal *(^nearbyPlacesSignal)(NSString *query, CLLocation *coordinate);
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context intent:(TGLocationPickerControllerIntent)intent;
|
||||
|
||||
- (void)setLiveLocationsSignal:(SSignal *)signal;
|
||||
@property (nonatomic, copy) SSignal *(^remainingTimeForMessage)(TGMessage *message);
|
||||
|
||||
@property (nonatomic, strong) id peer;
|
||||
@property (nonatomic, assign) bool allowLiveLocationSharing;
|
||||
|
||||
@end
|
@ -1,8 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGLocationPulseView : UIView
|
||||
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
|
||||
@end
|
@ -1,34 +0,0 @@
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
@class TGVenueAttachment;
|
||||
@class TGLocationMediaAttachment;
|
||||
|
||||
@interface TGLocationVenue : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSString *identifier;
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@property (nonatomic, readonly) NSString *displayAddress;
|
||||
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
|
||||
@property (nonatomic, readonly) NSString *categoryName;
|
||||
@property (nonatomic, readonly) NSURL *categoryIconUrl;
|
||||
|
||||
@property (nonatomic, readonly) NSString *provider;
|
||||
|
||||
@property (readonly, nonatomic) NSString *country;
|
||||
@property (readonly, nonatomic) NSString *state;
|
||||
@property (readonly, nonatomic) NSString *city;
|
||||
@property (readonly, nonatomic) NSString *address;
|
||||
@property (readonly, nonatomic) NSString *crossStreet;
|
||||
@property (readonly, nonatomic) NSString *street;
|
||||
|
||||
- (TGVenueAttachment *)venueAttachment;
|
||||
|
||||
+ (TGLocationVenue *)venueWithFoursquareDictionary:(NSDictionary *)dictionary;
|
||||
+ (TGLocationVenue *)venueWithGooglePlacesDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
+ (TGLocationVenue *)venueWithLocationAttachment:(TGLocationMediaAttachment *)attachment;
|
||||
|
||||
@end
|
||||
|
||||
extern NSString *const TGLocationGooglePlacesVenueProvider;
|
||||
extern NSString *const TGLocationFoursquareVenueProvider;
|
@ -1,59 +0,0 @@
|
||||
#import <LegacyComponents/LegacyComponentsContext.h>
|
||||
#import <LegacyComponents/TGLocationMapViewController.h>
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
@class TGLocationMediaAttachment;
|
||||
@class TGVenueAttachment;
|
||||
@class TGMenuSheetController;
|
||||
@class TGMessage;
|
||||
@class TGUser;
|
||||
|
||||
@interface TGLiveLocation : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) TGMessage *message;
|
||||
@property (nonatomic, strong, readonly) id peer;
|
||||
@property (nonatomic, readonly) bool hasOwnSession;
|
||||
@property (nonatomic, readonly) bool isOwnLocation;
|
||||
@property (nonatomic, readonly) bool isExpired;
|
||||
|
||||
- (instancetype)initWithMessage:(TGMessage *)message peer:(id)peer hasOwnSession:(bool)hasOwnSession isOwnLocation:(bool)isOwnLocation isExpired:(bool)isExpired;
|
||||
- (instancetype)initWithMessage:(TGMessage *)message peer:(id)peer;
|
||||
|
||||
- (int64_t)peerId;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGLocationViewController : TGLocationMapViewController;
|
||||
|
||||
@property (nonatomic, assign) bool modalMode;
|
||||
@property (nonatomic, assign) bool previewMode;
|
||||
|
||||
@property (nonatomic, assign) bool allowLiveLocationSharing;
|
||||
@property (nonatomic, assign) bool zoomToFitAllLocationsOnScreen;
|
||||
|
||||
|
||||
@property (nonatomic, copy) void (^presentActionsMenu)(TGLocationMediaAttachment *, bool);
|
||||
@property (nonatomic, copy) bool (^presentShareMenu)(TGMenuSheetController *, CLLocationCoordinate2D);
|
||||
@property (nonatomic, copy) bool (^presentOpenInMenu)(TGLocationViewController *, TGLocationMediaAttachment *, bool, void (^)(TGMenuSheetController *));
|
||||
@property (nonatomic, copy) void (^shareAction)(NSArray *peerIds, NSString *caption);
|
||||
|
||||
@property (nonatomic, copy) void (^openLocation)(TGMessage *message);
|
||||
@property (nonatomic, copy) void (^onViewDidAppear)(void);
|
||||
|
||||
@property (nonatomic, copy) void (^updateRightBarItem)(UIBarButtonItem *, bool, bool);
|
||||
|
||||
@property (nonatomic, readonly) UIButton *directionsButton;
|
||||
|
||||
@property (nonatomic, copy) SSignal *(^remainingTimeForMessage)(TGMessage *message);
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context liveLocation:(TGLiveLocation *)liveLocation;
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context locationAttachment:(TGLocationMediaAttachment *)locationAttachment peer:(id)peer color:(UIColor *)color;
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context message:(TGMessage *)message peer:(id)peer color:(UIColor *)color;
|
||||
|
||||
- (void)actionsButtonPressed;
|
||||
|
||||
- (void)setLiveLocationsSignal:(SSignal *)signal;
|
||||
- (void)setFrequentUpdatesHandle:(id<SDisposable>)disposable;
|
||||
|
||||
@end
|
@ -1,11 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGLocationWavesView : UIView
|
||||
|
||||
@property (nonatomic, strong) UIColor *color;
|
||||
|
||||
- (void)invalidate;
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
|
||||
@end
|
@ -50,4 +50,6 @@
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName;
|
||||
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab;
|
||||
|
||||
@end
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed;
|
||||
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
|
||||
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
|
||||
|
||||
@end
|
||||
|
@ -1,94 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TGSearchBar;
|
||||
|
||||
typedef enum {
|
||||
TGSearchBarStyleDefault = 0,
|
||||
TGSearchBarStyleDark = 1,
|
||||
TGSearchBarStyleLight = 2,
|
||||
TGSearchBarStyleLightPlain = 3,
|
||||
TGSearchBarStyleLightAlwaysPlain = 4,
|
||||
TGSearchBarStyleHeader = 5,
|
||||
TGSearchBarStyleKeyboard = 6
|
||||
} TGSearchBarStyle;
|
||||
|
||||
@protocol TGSearchBarDelegate <UISearchBarDelegate>
|
||||
|
||||
- (void)searchBar:(TGSearchBar *)searchBar willChangeHeight:(CGFloat)newHeight;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGSearchBarPallete : NSObject
|
||||
|
||||
@property (nonatomic, readonly) bool isDark;
|
||||
@property (nonatomic, readonly) UIColor *backgroundColor;
|
||||
@property (nonatomic, readonly) UIColor *highContrastBackgroundColor;
|
||||
@property (nonatomic, readonly) UIColor *textColor;
|
||||
@property (nonatomic, readonly) UIColor *placeholderColor;
|
||||
@property (nonatomic, readonly) UIImage *clearIcon;
|
||||
@property (nonatomic, readonly) UIColor *barBackgroundColor;
|
||||
@property (nonatomic, readonly) UIColor *barSeparatorColor;
|
||||
@property (nonatomic, readonly) UIColor *plainBackgroundColor;
|
||||
@property (nonatomic, readonly) UIColor *accentColor;
|
||||
@property (nonatomic, readonly) UIColor *accentContrastColor;
|
||||
@property (nonatomic, readonly) UIColor *menuBackgroundColor;
|
||||
@property (nonatomic, readonly) UIImage *segmentedControlBackgroundImage;
|
||||
@property (nonatomic, readonly) UIImage *segmentedControlSelectedImage;
|
||||
@property (nonatomic, readonly) UIImage *segmentedControlHighlightedImage;
|
||||
@property (nonatomic, readonly) UIImage *segmentedControlDividerImage;
|
||||
|
||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor highContrastBackgroundColor:(UIColor *)highContrastBackgroundColor textColor:(UIColor *)textColor placeholderColor:(UIColor *)placeholderColor clearIcon:(UIImage *)clearIcon barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor plainBackgroundColor:(UIColor *)plainBackgroundColor accentColor:(UIColor *)accentColor accentContrastColor:(UIColor *)accentContrastColor menuBackgroundColor:(UIColor *)menuBackgroundColor segmentedControlBackgroundImage:(UIImage *)segmentedControlBackgroundImage segmentedControlSelectedImage:(UIImage *)segmentedControlSelectedImage segmentedControlHighlightedImage:(UIImage *)segmentedControlHighlightedImage segmentedControlDividerImage:(UIImage *)segmentedControlDividerImage;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGSearchBar : UIView
|
||||
|
||||
+ (CGFloat)searchBarBaseHeight;
|
||||
+ (CGFloat)searchBarScopeHeight;
|
||||
- (CGFloat)baseHeight;
|
||||
|
||||
@property (nonatomic, copy) void (^clearPrefix)(bool);
|
||||
|
||||
@property (nonatomic, assign) UIEdgeInsets safeAreaInset;
|
||||
|
||||
@property (nonatomic, weak) id<TGSearchBarDelegate> delegate;
|
||||
@property (nonatomic) bool highContrast;
|
||||
|
||||
@property (nonatomic, strong) UITextField *customTextField;
|
||||
@property (nonatomic, readonly) UITextField *maybeCustomTextField;
|
||||
|
||||
@property (nonatomic, strong) UIImageView *customBackgroundView;
|
||||
@property (nonatomic, strong) UIImageView *customActiveBackgroundView;
|
||||
|
||||
@property (nonatomic, strong) NSArray *customScopeButtonTitles;
|
||||
@property (nonatomic) NSInteger selectedScopeButtonIndex;
|
||||
@property (nonatomic) bool showsScopeBar;
|
||||
@property (nonatomic) bool scopeBarCollapsed;
|
||||
|
||||
@property (nonatomic) bool searchBarShouldShowScopeControl;
|
||||
@property (nonatomic) bool alwaysExtended;
|
||||
@property (nonatomic) bool hidesCancelButton;
|
||||
|
||||
@property (nonatomic, strong) UIButton *customCancelButton;
|
||||
|
||||
@property (nonatomic) TGSearchBarStyle style;
|
||||
@property (nonatomic, strong) NSString *text;
|
||||
@property (nonatomic, strong) NSString *placeholder;
|
||||
@property (nonatomic, strong) NSAttributedString *prefixText;
|
||||
|
||||
@property (nonatomic) bool showActivity;
|
||||
@property (nonatomic) bool delayActivity;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(TGSearchBarStyle)style;
|
||||
|
||||
- (void)setShowsCancelButton:(bool)showsCancelButton animated:(bool)animated;
|
||||
|
||||
- (void)setCustomScopeBarHidden:(bool)hidden;
|
||||
|
||||
- (void)updateClipping:(CGFloat)clippedHeight;
|
||||
|
||||
- (void)localizationUpdated;
|
||||
|
||||
- (void)setPallete:(TGSearchBarPallete *)pallete;
|
||||
|
||||
@end
|
@ -1,46 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TGSearchDisplayMixin;
|
||||
@class TGSearchBar;
|
||||
|
||||
@protocol TGSearchDisplayMixinDelegate <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
- (UITableView *)createTableViewForSearchMixin:(TGSearchDisplayMixin *)searchMixin;
|
||||
- (UIView *)referenceViewForSearchResults;
|
||||
- (void)searchMixin:(TGSearchDisplayMixin *)searchMixin hasChangedSearchQuery:(NSString *)searchQuery withScope:(int)scope;
|
||||
|
||||
@optional
|
||||
|
||||
- (void)searchMixinWillActivate:(bool)animated;
|
||||
- (void)searchMixinWillDeactivate:(bool)animated;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGSearchDisplayMixin : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<TGSearchDisplayMixinDelegate> delegate;
|
||||
|
||||
@property (nonatomic, strong) TGSearchBar *searchBar;
|
||||
@property (nonatomic) bool isActive;
|
||||
@property (nonatomic, strong) UITableView *searchResultsTableView;
|
||||
@property (nonatomic) bool alwaysShowsCancelButton;
|
||||
|
||||
@property (nonatomic) bool searchResultsTableViewHidden;
|
||||
|
||||
@property (nonatomic) bool simpleLayout;
|
||||
|
||||
- (void)setSearchResultsTableViewHidden:(bool)searchResultsTableViewHidden animated:(bool)animated;
|
||||
|
||||
- (void)setIsActive:(bool)isActive animated:(bool)animated;
|
||||
|
||||
- (void)controllerInsetUpdated:(UIEdgeInsets)controllerInset;
|
||||
- (void)controllerLayoutUpdated:(CGSize)layoutSize;
|
||||
|
||||
- (void)reloadSearchResults;
|
||||
- (void)resignResponderIfAny;
|
||||
|
||||
- (void)unload;
|
||||
|
||||
@end
|
Before Width: | Height: | Size: 328 B |
Before Width: | Height: | Size: 640 B |
Before Width: | Height: | Size: 911 B |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 153 B |
Before Width: | Height: | Size: 424 B |
Before Width: | Height: | Size: 422 B |
Before Width: | Height: | Size: 534 B |
Before Width: | Height: | Size: 554 B |
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 623 B |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 155 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 139 B |
Before Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 908 B |
Before Width: | Height: | Size: 384 B |
Before Width: | Height: | Size: 740 B |
Before Width: | Height: | Size: 619 B |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 716 B |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 851 B |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 107 B |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 496 B |
Before Width: | Height: | Size: 645 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 916 B |
Before Width: | Height: | Size: 74 B |
Before Width: | Height: | Size: 105 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 902 B |
Before Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 749 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 914 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |