Merge commit '72dd8f1151cfd34e40ef4cf3986a8b2dd42eeb23'

This commit is contained in:
Peter 2019-07-17 17:45:30 +01:00
commit 093ec63cc5
70 changed files with 4168 additions and 3839 deletions

View File

@ -228,9 +228,7 @@
<false/> <false/>
<key>LSApplicationQueriesSchemes</key> <key>LSApplicationQueriesSchemes</key>
<array> <array>
<string>dbapi-3</string>
<string>instagram</string> <string>instagram</string>
<string>googledrive</string>
<string>comgooglemaps-x-callback</string> <string>comgooglemaps-x-callback</string>
<string>foursquare</string> <string>foursquare</string>
<string>here-location</string> <string>here-location</string>
@ -257,6 +255,7 @@
<string>opera-https</string> <string>opera-https</string>
<string>firefox-focus</string> <string>firefox-focus</string>
<string>ddgQuickLink</string> <string>ddgQuickLink</string>
<string>moovit</string>
</array> </array>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>

View File

@ -4471,3 +4471,6 @@ Any member of this group will be able to see messages in the channel.";
"Channel.TooMuchBots" = "Sorry, there are already too many bots in this group. Please remove some of the bots you're not using first."; "Channel.TooMuchBots" = "Sorry, there are already too many bots in this group. Please remove some of the bots you're not using first.";
"Channel.BotDoesntSupportGroups" = "Sorry, this bot is telling us it doesn't want to be added to groups. You can't add this bot unless its developers change their mind."; "Channel.BotDoesntSupportGroups" = "Sorry, this bot is telling us it doesn't want to be added to groups. You can't add this bot unless its developers change their mind.";
"StickerPacksSettings.AnimatedStickers" = "Loop Animated Stickers";
"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers will play in chat continuously.";

View File

@ -149,6 +149,12 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
public final var rotated = false public final var rotated = false
public final var experimentalSnapScrollToItem = false public final var experimentalSnapScrollToItem = false
public final var scrollEnabled: Bool = true {
didSet {
self.scroller.isScrollEnabled = self.scrollEnabled
}
}
private final var invisibleInset: CGFloat = 500.0 private final var invisibleInset: CGFloat = 500.0
public var preloadPages: Bool = true { public var preloadPages: Bool = true {
didSet { didSet {

View File

@ -5,6 +5,14 @@ import CoreText
private let defaultFont = UIFont.systemFont(ofSize: 15.0) private let defaultFont = UIFont.systemFont(ofSize: 15.0)
private final class TextNodeStrikethrough {
let frame: CGRect
init(frame: CGRect) {
self.frame = frame
}
}
private final class TextNodeLine { private final class TextNodeLine {
let line: CTLine let line: CTLine
let frame: CGRect let frame: CGRect
@ -21,7 +29,7 @@ private final class TextNodeLine {
} }
} }
private final class TextNodeStrikethrough { private final class TextNodeBlockQuote {
let frame: CGRect let frame: CGRect
init(frame: CGRect) { init(frame: CGRect) {
@ -83,8 +91,9 @@ public final class TextNodeLayoutArguments {
public let lineSpacing: CGFloat public let lineSpacing: CGFloat
public let cutout: TextNodeCutout? public let cutout: TextNodeCutout?
public let insets: UIEdgeInsets public let insets: UIEdgeInsets
public let lineColor: UIColor?
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets()) { public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil) {
self.attributedString = attributedString self.attributedString = attributedString
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.maximumNumberOfLines = maximumNumberOfLines self.maximumNumberOfLines = maximumNumberOfLines
@ -94,6 +103,7 @@ public final class TextNodeLayoutArguments {
self.lineSpacing = lineSpacing self.lineSpacing = lineSpacing
self.cutout = cutout self.cutout = cutout
self.insets = insets self.insets = insets
self.lineColor = lineColor
} }
} }
@ -111,9 +121,11 @@ public final class TextNodeLayout: NSObject {
public let truncated: Bool public let truncated: Bool
fileprivate let firstLineOffset: CGFloat fileprivate let firstLineOffset: CGFloat
fileprivate let lines: [TextNodeLine] fileprivate let lines: [TextNodeLine]
fileprivate let blockQuotes: [TextNodeBlockQuote]
fileprivate let lineColor: UIColor?
public let hasRTL: Bool public let hasRTL: Bool
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], backgroundColor: UIColor?) { fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?) {
self.attributedString = attributedString self.attributedString = attributedString
self.maximumNumberOfLines = maximumNumberOfLines self.maximumNumberOfLines = maximumNumberOfLines
self.truncationType = truncationType self.truncationType = truncationType
@ -126,7 +138,9 @@ public final class TextNodeLayout: NSObject {
self.truncated = truncated self.truncated = truncated
self.firstLineOffset = firstLineOffset self.firstLineOffset = firstLineOffset
self.lines = lines self.lines = lines
self.blockQuotes = blockQuotes
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.lineColor = lineColor
var hasRTL = false var hasRTL = false
for line in lines { for line in lines {
if line.isRTL { if line.isRTL {
@ -561,7 +575,7 @@ public class TextNode: ASDisplayNode {
} }
} }
private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets) -> TextNodeLayout { private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?) -> TextNodeLayout {
if let attributedString = attributedString { if let attributedString = attributedString {
let stringLength = attributedString.length let stringLength = attributedString.length
@ -582,11 +596,12 @@ public class TextNode: ASDisplayNode {
let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor) let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor)
var lines: [TextNodeLine] = [] var lines: [TextNodeLine] = []
var blockQuotes: [TextNodeBlockQuote] = []
var maybeTypesetter: CTTypesetter? var maybeTypesetter: CTTypesetter?
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
if maybeTypesetter == nil { if maybeTypesetter == nil {
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor)
} }
let typesetter = maybeTypesetter! let typesetter = maybeTypesetter!
@ -682,11 +697,28 @@ public class TextNode: ASDisplayNode {
truncated = true truncated = true
} }
var headIndent: CGFloat = 0.0
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedStringKey.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
} else if let paragraphStyle = attributes[NSAttributedStringKey.paragraphStyle] as? NSParagraphStyle {
headIndent = paragraphStyle.headIndent
}
}
let lineWidth = min(constrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine)))) let lineWidth = min(constrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))))
let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: fontLineHeight) let lineFrame = CGRect(x: lineCutoutOffset + headIndent, y: lineOriginY, width: lineWidth, height: fontLineHeight)
layoutSize.height += fontLineHeight + fontLineSpacing layoutSize.height += fontLineHeight + fontLineSpacing
layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth)
if headIndent > 0.0 {
blockQuotes.append(TextNodeBlockQuote(frame: lineFrame))
}
var isRTL = false var isRTL = false
let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray
if glyphRuns.count != 0 { if glyphRuns.count != 0 {
@ -696,16 +728,7 @@ public class TextNode: ASDisplayNode {
} }
} }
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedStringKey.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
}
}
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs)) lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs))
break break
} else { } else {
if lineCharacterCount > 0 { if lineCharacterCount > 0 {
@ -719,11 +742,27 @@ public class TextNode: ASDisplayNode {
let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 100.0) let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 100.0)
lastLineCharacterIndex += lineCharacterCount lastLineCharacterIndex += lineCharacterCount
var headIndent: CGFloat = 0.0
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedStringKey.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
} else if let paragraphStyle = attributes[NSAttributedStringKey.paragraphStyle] as? NSParagraphStyle {
headIndent = paragraphStyle.headIndent
}
}
let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine)))
let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: fontLineHeight) let lineFrame = CGRect(x: lineCutoutOffset + headIndent, y: lineOriginY, width: lineWidth, height: fontLineHeight)
layoutSize.height += fontLineHeight layoutSize.height += fontLineHeight
layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth)
if headIndent > 0.0 {
blockQuotes.append(TextNodeBlockQuote(frame: lineFrame))
}
var isRTL = false var isRTL = false
let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray
if glyphRuns.count != 0 { if glyphRuns.count != 0 {
@ -733,14 +772,6 @@ public class TextNode: ASDisplayNode {
} }
} }
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedStringKey.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
}
}
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs)) lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs))
} else { } else {
if !lines.isEmpty { if !lines.isEmpty {
@ -762,9 +793,9 @@ public class TextNode: ASDisplayNode {
} }
} }
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor)
} else { } else {
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor)
} }
} }
@ -799,11 +830,8 @@ public class TextNode: ASDisplayNode {
let textMatrix = context.textMatrix let textMatrix = context.textMatrix
let textPosition = context.textPosition let textPosition = context.textPosition
//CGContextSaveGState(context)
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
//let clipRect = CGContextGetClipBoundingBox(context)
let alignment = layout.alignment let alignment = layout.alignment
let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) let offset = CGPoint(x: layout.insets.left, y: layout.insets.top)
@ -832,7 +860,34 @@ public class TextNode: ASDisplayNode {
} }
} }
//CGContextRestoreGState(context) var blockQuoteFrames: [CGRect] = []
var currentBlockQuoteFrame: CGRect?
for blockQuote in layout.blockQuotes {
if let frame = currentBlockQuoteFrame {
if blockQuote.frame.minY - frame.maxY < 20.0 {
currentBlockQuoteFrame = frame.union(blockQuote.frame)
} else {
blockQuoteFrames.append(frame)
currentBlockQuoteFrame = frame
}
} else {
currentBlockQuoteFrame = blockQuote.frame
}
}
if let frame = currentBlockQuoteFrame {
blockQuoteFrames.append(frame)
}
for frame in blockQuoteFrames {
if let lineColor = layout.lineColor {
context.setFillColor(lineColor.cgColor)
}
let rect = UIBezierPath(roundedRect: CGRect(x: frame.minX - 9.0, y: frame.minY - 14.0, width: 2.0, height: frame.height), cornerRadius: 1.0)
context.addPath(rect.cgPath)
context.fillPath()
}
context.textMatrix = textMatrix context.textMatrix = textMatrix
context.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) context.textPosition = CGPoint(x: textPosition.x, y: textPosition.y)
} }
@ -872,11 +927,11 @@ public class TextNode: ASDisplayNode {
if stringMatch { if stringMatch {
layout = existingLayout layout = existingLayout
} else { } else {
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets) layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor)
updated = true updated = true
} }
} else { } else {
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets) layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor)
updated = true updated = true
} }

View File

@ -2075,6 +2075,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
var isContactUpdates: [(PeerId, Bool)] = [] var isContactUpdates: [(PeerId, Bool)] = []
var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = []
var recentlyUsedStickers: [MediaId: (MessageIndex, TelegramMediaFile)] = [:] var recentlyUsedStickers: [MediaId: (MessageIndex, TelegramMediaFile)] = [:]
var slowModeLastMessageTimeouts:[PeerId : Int32] = [:]
var recentlyUsedGifs: [MediaId: (MessageIndex, TelegramMediaFile)] = [:] var recentlyUsedGifs: [MediaId: (MessageIndex, TelegramMediaFile)] = [:]
var syncRecentGifs = false var syncRecentGifs = false
var langPackDifferences: [String: [Api.LangPackDifference]] = [:] var langPackDifferences: [String: [Api.LangPackDifference]] = [:]
@ -2181,6 +2182,11 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
} }
} }
} }
if !message.flags.contains(.Incoming) && !message.flags.contains(.Unsent) {
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
slowModeLastMessageTimeouts[message.id.peerId] = max(slowModeLastMessageTimeouts[message.id.peerId] ?? 0, message.timestamp)
}
}
if !message.flags.contains(.Incoming), message.forwardInfo == nil { if !message.flags.contains(.Incoming), message.forwardInfo == nil {
inner: for media in message.media { inner: for media in message.media {
@ -2713,6 +2719,22 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
} }
} }
if !slowModeLastMessageTimeouts.isEmpty {
var peerIds:Set<PeerId> = Set()
var cachedDatas:[PeerId : CachedChannelData] = [:]
for (peerId, timeout) in slowModeLastMessageTimeouts {
var cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData ?? CachedChannelData()
if let slowModeTimeout = cachedData.slowModeTimeout {
cachedData = cachedData.withUpdatedSlowModeValidUntilTimestamp(slowModeValidUntilTimestamp: timeout + slowModeTimeout)
peerIds.insert(peerId)
cachedDatas[peerId] = cachedData
}
}
transaction.updatePeerCachedData(peerIds: peerIds, update: { peerId, current in
return cachedDatas[peerId] ?? current
})
}
if syncRecentGifs { if syncRecentGifs {
addSynchronizeSavedGifsOperation(transaction: transaction, operation: .sync) addSynchronizeSavedGifsOperation(transaction: transaction, operation: .sync)
} else { } else {

View File

@ -73,7 +73,10 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
let info: TelegramChannelInfo let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 { if (flags & Int32(1 << 8)) != 0 {
let infoFlags = TelegramChannelGroupFlags() var infoFlags = TelegramChannelGroupFlags()
if (flags & Int32(1 << 22)) != 0 {
infoFlags.insert(.isEnabledSlowMode)
}
info = .group(TelegramChannelGroupInfo(flags: infoFlags)) info = .group(TelegramChannelGroupInfo(flags: infoFlags))
} else { } else {
var infoFlags = TelegramChannelBroadcastFlags() var infoFlags = TelegramChannelBroadcastFlags()

View File

@ -16,7 +16,7 @@ import Foundation
#endif #endif
public struct AppUpdateInfo: Equatable { public struct AppUpdateInfo: Equatable {
public let popup: Bool public let blocking: Bool
public let version: String public let version: String
public let text: String public let text: String
public let entities: [MessageTextEntity] public let entities: [MessageTextEntity]
@ -26,7 +26,7 @@ extension AppUpdateInfo {
init?(apiAppUpdate: Api.help.AppUpdate) { init?(apiAppUpdate: Api.help.AppUpdate) {
switch apiAppUpdate { switch apiAppUpdate {
case let .appUpdate(flags, _, version, text, entities, _, _): case let .appUpdate(flags, _, version, text, entities, _, _):
self.popup = (flags & (1 << 0)) != 0 self.blocking = (flags & (1 << 0)) != 0
self.version = version self.version = version
self.text = text self.text = text
self.entities = messageTextEntitiesFromApiEntities(entities) self.entities = messageTextEntitiesFromApiEntities(entities)

View File

@ -165,6 +165,20 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities) let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities)
if currentMessage.id.peerId.namespace == Namespaces.Peer.CloudChannel, !currentMessage.flags.contains(.Incoming) {
let peerId = currentMessage.id.peerId
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, current in
var cachedData = current as? CachedChannelData ?? CachedChannelData()
if let slowModeTimeout = cachedData.slowModeTimeout {
cachedData = cachedData.withUpdatedSlowModeValidUntilTimestamp(slowModeValidUntilTimestamp: currentMessage.timestamp + slowModeTimeout)
return cachedData
} else {
return current
}
})
}
return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media))
}) })
for file in sentStickers { for file in sentStickers {
@ -173,6 +187,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
for file in sentGifs { for file in sentGifs {
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200) transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200)
} }
stateManager.addUpdates(result) stateManager.addUpdates(result)
} }
} }

View File

@ -42,7 +42,7 @@ func augmentMediaWithReference(_ mediaReference: AnyMediaReference) -> Media {
if file.partialReference != nil { if file.partialReference != nil {
return file return file
} else { } else {
return file.withUpdatedPartialReference(mediaReference.partial) return file.withUpdatedPartialReference(mediaReference.partial)
} }
} else if let image = mediaReference.media as? TelegramMediaImage { } else if let image = mediaReference.media as? TelegramMediaImage {
if image.partialReference != nil { if image.partialReference != nil {
@ -367,7 +367,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
} }
let authorId: PeerId? let authorId: PeerId?
if let peer = peer as? TelegramChannel, case let .broadcast(info) = peer.info { if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
authorId = peer.id authorId = peer.id
} else { } else {
authorId = account.peerId authorId = account.peerId
@ -388,12 +388,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] { if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] {
if let peer = peer as? TelegramSecretChat { if let peer = peer as? TelegramSecretChat {
var isAction = false var isAction = false
var mediaDuration: Int32?
for media in sourceMessage.media { for media in sourceMessage.media {
if let _ = media as? TelegramMediaAction { if let _ = media as? TelegramMediaAction {
isAction = true isAction = true
} else if let file = media as? TelegramMediaFile, let duration = file.duration {
mediaDuration = duration
} }
} }
if !disableAutoremove, let messageAutoremoveTimeout = peer.messageAutoremoveTimeout, !isAction { if !disableAutoremove, let messageAutoremoveTimeout = peer.messageAutoremoveTimeout, !isAction {

View File

@ -476,17 +476,17 @@ private enum UploadedMediaFileAndThumbnail {
private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference) -> Signal<Api.InputFile?, PendingMessageUploadError> { private func uploadedThumbnail(network: Network, postbox: Postbox, resourceReference: MediaResourceReference) -> Signal<Api.InputFile?, PendingMessageUploadError> {
return multipartUpload(network: network, postbox: postbox, source: .resource(resourceReference), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false) return multipartUpload(network: network, postbox: postbox, source: .resource(resourceReference), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false)
|> mapError { _ -> PendingMessageUploadError in return .generic } |> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in |> mapToSignal { result -> Signal<Api.InputFile?, PendingMessageUploadError> in
switch result { switch result {
case .progress: case .progress:
return .complete() return .complete()
case let .inputFile(inputFile): case let .inputFile(inputFile):
return .single(inputFile) return .single(inputFile)
case .inputSecretFile: case .inputSecretFile:
return .single(nil) return .single(nil)
}
} }
}
} }
public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileAttribute]) -> MediaResourceStatsCategory { public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileAttribute]) -> MediaResourceStatsCategory {

View File

@ -30,11 +30,8 @@ public func updateChannelSlowModeInteractively(postbox: Postbox, network: Networ
accountStateManager.addUpdates(updates) accountStateManager.addUpdates(updates)
return postbox.transaction { transaction -> Void in return postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, currentData in transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, currentData in
if let currentData = currentData as? CachedChannelData { let currentData = currentData as? CachedChannelData ?? CachedChannelData()
return currentData.withUpdatedSlowModeTimeout(slowModeTimeout: timeout) return currentData.withUpdatedSlowModeTimeout(slowModeTimeout: timeout)
} else {
return currentData
}
}) })
} }
|> introduceError(UpdateChannelSlowModeError.self) |> introduceError(UpdateChannelSlowModeError.self)

View File

@ -21,6 +21,20 @@ public struct StandaloneUploadSecretFile {
let key: SecretFileEncryptionKey let key: SecretFileEncryptionKey
} }
public enum StandaloneUploadMediaThumbnailResult {
case pending
case file(Api.InputFile)
case none
var file: Api.InputFile? {
if case let .file(file) = self {
return file
} else {
return nil
}
}
}
public enum StandaloneUploadMediaResult { public enum StandaloneUploadMediaResult {
case media(AnyMediaReference) case media(AnyMediaReference)
} }
@ -30,7 +44,22 @@ public enum StandaloneUploadMediaEvent {
case result(StandaloneUploadMediaResult) case result(StandaloneUploadMediaResult)
} }
public func standaloneUploadedImage(account: Account, peerId: PeerId, text: String, data: Data, dimensions: CGSize) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> { private func uploadedThumbnail(network: Network, postbox: Postbox, data: Data) -> Signal<Api.InputFile?, StandaloneUploadMediaError> {
return multipartUpload(network: network, postbox: postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false)
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { result -> Signal<Api.InputFile?, StandaloneUploadMediaError> in
switch result {
case .progress:
return .complete()
case let .inputFile(inputFile):
return .single(inputFile)
case .inputSecretFile:
return .single(nil)
}
}
}
public func standaloneUploadedImage(account: Account, peerId: PeerId, text: String, data: Data, thumbnailData: Data? = nil, dimensions: CGSize) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false) return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false)
|> mapError { _ -> StandaloneUploadMediaError in return .generic } |> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in |> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
@ -91,65 +120,99 @@ public func standaloneUploadedImage(account: Account, peerId: PeerId, text: Stri
} }
} }
public func standaloneUploadedFile(account: Account, peerId: PeerId, text: String, source: MultipartUploadSource, mimeType: String, attributes: [TelegramMediaFileAttribute], hintFileIsLarge: Bool) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> { public func standaloneUploadedFile(account: Account, peerId: PeerId, text: String, source: MultipartUploadSource, thumbnailData: Data? = nil, mimeType: String, attributes: [TelegramMediaFileAttribute], hintFileIsLarge: Bool) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: source, encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: statsCategoryForFileWithAttributes(attributes)), hintFileSize: nil, hintFileIsLarge: hintFileIsLarge) let upload = multipartUpload(network: account.network, postbox: account.postbox, source: source, encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: statsCategoryForFileWithAttributes(attributes)), hintFileSize: nil, hintFileIsLarge: hintFileIsLarge)
|> mapError { _ -> StandaloneUploadMediaError in return .generic } |> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch next { let uploadThumbnail: Signal<StandaloneUploadMediaThumbnailResult, StandaloneUploadMediaError>
case let .inputFile(inputFile): if let thumbnailData = thumbnailData {
return account.postbox.transaction { transaction -> Api.InputPeer? in uploadThumbnail = .single(.pending)
return transaction.getPeer(peerId).flatMap(apiInputPeer) |> then(
} uploadedThumbnail(network: account.network, postbox: account.postbox, data: thumbnailData)
|> mapError { _ -> StandaloneUploadMediaError in return .generic } |> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { inputPeer -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in |> map { result in
if let inputPeer = inputPeer { if let result = result {
return account.network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, ttlSeconds: nil))) return .file(result)
|> mapError { _ -> StandaloneUploadMediaError in return .generic } } else {
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in return .none
switch media {
case let .messageMediaDocument(_, document, _):
if let document = document {
if let mediaFile = telegramMediaFileFromApiDocument(document) {
return .single(.result(.media(.standalone(media: mediaFile))))
}
}
default:
break
}
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
case let .inputSecretFile(file, size, key):
return account.postbox.transaction { transaction -> Api.InputEncryptedChat? in
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
return Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)
}
return nil
}
|> introduceError(StandaloneUploadMediaError.self)
|> mapToSignal { inputChat -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
guard let inputChat = inputChat else {
return .fail(.generic)
}
return account.network.request(Api.functions.messages.uploadEncryptedFile(peer: inputChat, file: file))
|> mapError { _ -> StandaloneUploadMediaError in return .generic
}
|> mapToSignal { result -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch result {
case let .encryptedFile(id, accessHash, size, dcId, _):
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: size, datacenterId: Int(dcId), key: key), previewRepresentations: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: attributes)
return .single(.result(.media(.standalone(media: media))))
case .encryptedFileEmpty:
return .fail(.generic)
}
}
} }
}
)
} else {
uploadThumbnail = .single(.none)
}
return combineLatest(upload, uploadThumbnail)
|> mapToSignal { result, thumbnail in
switch result {
case let .progress(progress): case let .progress(progress):
return .single(.progress(progress)) return .single(.progress(progress))
} default:
switch thumbnail {
case .pending:
return .complete()
default:
switch result {
case let .inputFile(inputFile):
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { inputPeer -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
if let inputPeer = inputPeer {
var flags: Int32 = 0
let thumbnailFile = thumbnail.file
if let _ = thumbnailFile {
flags |= 1 << 2
}
return account.network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, ttlSeconds: nil)))
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch media {
case let .messageMediaDocument(_, document, _):
if let document = document {
if let mediaFile = telegramMediaFileFromApiDocument(document) {
return .single(.result(.media(.standalone(media: mediaFile))))
}
}
default:
break
}
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
case let .inputSecretFile(file, _, key):
return account.postbox.transaction { transaction -> Api.InputEncryptedChat? in
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
return Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)
}
return nil
}
|> introduceError(StandaloneUploadMediaError.self)
|> mapToSignal { inputChat -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
guard let inputChat = inputChat else {
return .fail(.generic)
}
return account.network.request(Api.functions.messages.uploadEncryptedFile(peer: inputChat, file: file))
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { result -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch result {
case let .encryptedFile(id, accessHash, size, dcId, _):
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: size, datacenterId: Int(dcId), key: key), previewRepresentations: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int(size), attributes: attributes)
return .single(.result(.media(.standalone(media: media))))
case .encryptedFileEmpty:
return .fail(.generic)
}
}
}
case .progress:
return .never()
}
}
}
} }
} }

View File

@ -72,6 +72,7 @@ public struct TelegramChannelGroupFlags: OptionSet {
public init(rawValue: Int32) { public init(rawValue: Int32) {
self.rawValue = rawValue self.rawValue = rawValue
} }
public static let isEnabledSlowMode = TelegramChannelGroupFlags(rawValue: 1 << 0)
} }
public struct TelegramChannelGroupInfo: Equatable { public struct TelegramChannelGroupInfo: Equatable {

View File

@ -522,7 +522,7 @@ final class AuthorizedApplicationContext {
strongSelf.currentAppUpdateInfo = appUpdateInfo strongSelf.currentAppUpdateInfo = appUpdateInfo
if let appUpdateInfo = appUpdateInfo { if let appUpdateInfo = appUpdateInfo {
let controller = updateInfoController(context: strongSelf.context, appUpdateInfo: appUpdateInfo) let controller = updateInfoController(context: strongSelf.context, appUpdateInfo: appUpdateInfo)
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) strongSelf.mainWindow.present(controller, on: .update)
} }
})) }))

View File

@ -5,6 +5,7 @@ import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences
public enum ArchivedStickerPacksControllerMode { public enum ArchivedStickerPacksControllerMode {
case stickers case stickers
@ -65,7 +66,7 @@ private enum ArchivedStickerPacksEntryId: Hashable {
private enum ArchivedStickerPacksEntry: ItemListNodeEntry { private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
case info(PresentationTheme, String) case info(PresentationTheme, String)
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, ItemListStickerPackItemEditing) case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, Bool, ItemListStickerPackItemEditing)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -78,7 +79,7 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
switch self { switch self {
case .info: case .info:
return .index(0) return .index(0)
case let .pack(_, _, _, info, _, _, _, _): case let .pack(_, _, _, info, _, _, _, _, _):
return .pack(info.id) return .pack(info.id)
} }
} }
@ -91,8 +92,8 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsTopItem, lhsCount, lhsEnabled, lhsEditing): case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsTopItem, lhsCount, lhsPlayAnimatedStickers, lhsEnabled, lhsEditing):
if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsTopItem, rhsCount, rhsEnabled, rhsEditing) = rhs { if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsTopItem, rhsCount, rhsPlayAnimatedStickers, rhsEnabled, rhsEditing) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -111,6 +112,9 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
if lhsCount != rhsCount { if lhsCount != rhsCount {
return false return false
} }
if lhsPlayAnimatedStickers != rhsPlayAnimatedStickers {
return false
}
if lhsEnabled != rhsEnabled { if lhsEnabled != rhsEnabled {
return false return false
} }
@ -133,9 +137,9 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
default: default:
return true return true
} }
case let .pack(lhsIndex, _, _, _, _, _, _, _): case let .pack(lhsIndex, _, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case let .pack(rhsIndex, _, _, _, _, _, _, _): case let .pack(rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
default: default:
return false return false
@ -147,8 +151,8 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
switch self { switch self {
case let .info(theme, text): case let .info(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .pack(_, theme, strings, info, topItem, count, enabled, editing): case let .pack(_, theme, strings, info, topItem, count, animatedStickers, enabled, editing):
return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .installation(installed: false), editing: editing, enabled: enabled, sectionId: self.section, action: { return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .installation(installed: false), editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info) arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { current, previous in }, setPackIdWithRevealedOptions: { current, previous in
arguments.setPackIdWithRevealedOptions(current, previous) arguments.setPackIdWithRevealedOptions(current, previous)
@ -205,7 +209,7 @@ private struct ArchivedStickerPacksControllerState: Equatable {
} }
} }
private func archivedStickerPacksControllerEntries(presentationData: PresentationData, state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedView: CombinedView) -> [ArchivedStickerPacksEntry] { private func archivedStickerPacksControllerEntries(presentationData: PresentationData, state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedView: CombinedView, stickerSettings: StickerSettings) -> [ArchivedStickerPacksEntry] {
var entries: [ArchivedStickerPacksEntry] = [] var entries: [ArchivedStickerPacksEntry] = []
if let packs = packs { if let packs = packs {
@ -219,7 +223,7 @@ private func archivedStickerPacksControllerEntries(presentationData: Presentatio
var index: Int32 = 0 var index: Int32 = 0
for item in packs { for item in packs {
if !installedIds.contains(item.info.id) { if !installedIds.contains(item.info.id) {
entries.append(.pack(index, presentationData.theme, presentationData.strings, item.info, item.topItems.first, presentationData.strings.StickerPack_StickerCount(item.info.count), !state.removingPackIds.contains(item.info.id), ItemListStickerPackItemEditing(editable: true, editing: state.editing, revealed: state.packIdWithRevealedOptions == item.info.id, reorderable: false))) entries.append(.pack(index, presentationData.theme, presentationData.strings, item.info, item.topItems.first, presentationData.strings.StickerPack_StickerCount(item.info.count), stickerSettings.loopAnimatedStickers, !state.removingPackIds.contains(item.info.id), ItemListStickerPackItemEditing(editable: true, editing: state.editing, revealed: state.packIdWithRevealedOptions == item.info.id, reorderable: false)))
index += 1 index += 1
} }
} }
@ -369,9 +373,14 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
var previousPackCount: Int? var previousPackCount: Int?
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, installedStickerPacks.get() |> deliverOnMainQueue) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, installedStickerPacks.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, packs, installedView -> (ItemListControllerState, (ItemListNodeState<ArchivedStickerPacksEntry>, ArchivedStickerPacksEntry.ItemGenerationArguments)) in |> map { presentationData, state, packs, installedView, sharedData -> (ItemListControllerState, (ItemListNodeState<ArchivedStickerPacksEntry>, ArchivedStickerPacksEntry.ItemGenerationArguments)) in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value
}
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
if let packs = packs, packs.count != 0 { if let packs = packs, packs.count != 0 {
if state.editing { if state.editing {
@ -399,7 +408,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.StickerPacksSettings_ArchivedPacks), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.StickerPacksSettings_ArchivedPacks), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: archivedStickerPacksControllerEntries(presentationData: presentationData, state: state, packs: packs, installedView: installedView), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previous != nil && packs != nil && (previous! != 0 && previous! >= packs!.count - 10)) let listState = ItemListNodeState(entries: archivedStickerPacksControllerEntries(presentationData: presentationData, state: state, packs: packs, installedView: installedView, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previous != nil && packs != nil && (previous! != 0 && previous! >= packs!.count - 10))
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {
actionsDisposable.dispose() actionsDisposable.dispose()

View File

@ -277,7 +277,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
} }
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let alertTheme = AlertControllerTheme(presentationTheme: strongSelf.theme) let alertTheme = AlertControllerTheme(presentationTheme: strongSelf.theme)
let attributedText = stringWithAppliedEntities(termsOfService.text, entities: termsOfService.entities, baseColor: alertTheme.primaryColor, linkColor: alertTheme.accentColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.regular(13.0)) let attributedText = stringWithAppliedEntities(termsOfService.text, entities: termsOfService.entities, baseColor: alertTheme.primaryColor, linkColor: alertTheme.accentColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.regular(13.0), blockQuoteFont: Font.regular(13.0))
let contentNode = TextAlertContentNode(theme: alertTheme, title: NSAttributedString(string: strongSelf.strings.Login_TermsOfServiceHeader, font: Font.medium(17.0), textColor: alertTheme.primaryColor, paragraphAlignment: .center), text: attributedText, actions: [ let contentNode = TextAlertContentNode(theme: alertTheme, title: NSAttributedString(string: strongSelf.strings.Login_TermsOfServiceHeader, font: Font.medium(17.0), textColor: alertTheme.primaryColor, paragraphAlignment: .center), text: attributedText, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.strings.Login_TermsOfServiceAgree, action: { TextAlertAction(type: .defaultAction, title: strongSelf.strings.Login_TermsOfServiceAgree, action: {
dismissImpl?() dismissImpl?()

View File

@ -288,7 +288,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext:
previousState = blockedPeersState previousState = blockedPeersState
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.BlockedUsers_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.BlockedUsers_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: blockedPeersControllerEntries(presentationData: presentationData, state: state, blockedPeersState: blockedPeersState), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previousStateValue != nil && previousStateValue!.peers.count >= blockedPeersState.peers.count) let listState = ItemListNodeState(entries: blockedPeersControllerEntries(presentationData: presentationData, state: state, blockedPeersState: blockedPeersState), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: previousStateValue != nil && previousStateValue!.peers.count >= blockedPeersState.peers.count, scrollEnabled: emptyStateItem == nil)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -152,7 +152,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
updatedTextAndEntities = (item.text, generateTextEntities(item.text, enabledTypes: .all)) updatedTextAndEntities = (item.text, generateTextEntities(item.text, enabledTypes: .all))
} }
let attributedText = stringWithAppliedEntities(updatedTextAndEntities.0, entities: updatedTextAndEntities.1, baseColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor, linkColor: item.presentationData.theme.theme.chat.message.infoLinkTextColor, baseFont: messageFont, linkFont: messageFont, boldFont: messageBoldFont, italicFont: messageItalicFont, boldItalicFont: messageBoldItalicFont, fixedFont: messageFixedFont) let attributedText = stringWithAppliedEntities(updatedTextAndEntities.0, entities: updatedTextAndEntities.1, baseColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor, linkColor: item.presentationData.theme.theme.chat.message.infoLinkTextColor, baseFont: messageFont, linkFont: messageFont, boldFont: messageBoldFont, italicFont: messageItalicFont, boldItalicFont: messageBoldItalicFont, fixedFont: messageFixedFont, blockQuoteFont: messageFont)
let horizontalEdgeInset: CGFloat = 10.0 + params.leftInset let horizontalEdgeInset: CGFloat = 10.0 + params.leftInset
let horizontalContentInset: CGFloat = 12.0 let horizontalContentInset: CGFloat = 12.0

View File

@ -208,6 +208,9 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
private var automaticMediaDownloadSettingsDisposable: Disposable? private var automaticMediaDownloadSettingsDisposable: Disposable?
private var stickerSettings: ChatInterfaceStickerSettings
private var stickerSettingsDisposable: Disposable?
private var applicationInForegroundDisposable: Disposable? private var applicationInForegroundDisposable: Disposable?
private var checkedPeerChatServiceActions = false private var checkedPeerChatServiceActions = false
@ -264,6 +267,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.automaticMediaDownloadSettings = context.sharedContext.currentAutomaticMediaDownloadSettings.with { $0 } self.automaticMediaDownloadSettings = context.sharedContext.currentAutomaticMediaDownloadSettings.with { $0 }
self.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: false)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.fontSize, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation) self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.fontSize, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation)
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
@ -1378,8 +1383,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}, cancelInteractiveKeyboardGestures: { [weak self] in }, cancelInteractiveKeyboardGestures: { [weak self] in
(self?.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() (self?.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
self?.chatDisplayNode.cancelInteractiveKeyboardGestures() self?.chatDisplayNode.cancelInteractiveKeyboardGestures()
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings)
pollActionState: ChatInterfacePollActionState())
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
@ -1884,6 +1888,24 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
}) })
self.stickerSettingsDisposable = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings])
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value
}
let chatStickerSettings = ChatInterfaceStickerSettings(stickerSettings: stickerSettings)
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings {
strongSelf.stickerSettings = chatStickerSettings
strongSelf.controllerInteraction?.stickerSettings = chatStickerSettings
if strongSelf.isNodeLoaded {
strongSelf.chatDisplayNode.updateStickerSettings(chatStickerSettings)
}
}
})
self.applicationInForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground self.applicationInForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|> distinctUntilChanged |> distinctUntilChanged
|> deliverOn(Queue.mainQueue())).start(next: { [weak self] value in |> deliverOn(Queue.mainQueue())).start(next: { [weak self] value in
@ -6843,23 +6865,22 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
} }
}) })
] ]
}
if let message = self.chatDisplayNode.historyNode.latestMessageInCurrentHistoryView(), !message.flags.contains(.Incoming) {
inputShortcuts.append(KeyShortcut(input: UIKeyInputUpArrow, action: { [weak self] in var canEdit = false
if let strongSelf = self { self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
var canEdit = false if state.interfaceState.effectiveInputState.inputText.length == 0 && state.interfaceState.editMessage == nil {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in canEdit = true
if state.interfaceState.effectiveInputState.inputText.length == 0 && state.interfaceState.editMessage == nil {
canEdit = true
}
return state
})
if canEdit {
strongSelf.interfaceInteraction?.setupEditMessage(message.id)
}
}
}))
} }
return state
})
if canEdit, let message = self.chatDisplayNode.historyNode.firstMessageForEditInCurrentHistoryView() {
inputShortcuts.append(KeyShortcut(input: UIKeyInputUpArrow, action: { [weak self] in
if let strongSelf = self {
strongSelf.interfaceInteraction?.setupEditMessage(message.id)
}
}))
} }
let otherShortcuts: [KeyShortcut] = [ let otherShortcuts: [KeyShortcut] = [

View File

@ -31,6 +31,22 @@ struct ChatInterfaceHighlightedState: Equatable {
} }
} }
struct ChatInterfaceStickerSettings: Equatable {
let loopAnimatedStickers: Bool
public init(loopAnimatedStickers: Bool) {
self.loopAnimatedStickers = loopAnimatedStickers
}
public init(stickerSettings: StickerSettings) {
self.loopAnimatedStickers = stickerSettings.loopAnimatedStickers
}
static func ==(lhs: ChatInterfaceStickerSettings, rhs: ChatInterfaceStickerSettings) -> Bool {
return lhs.loopAnimatedStickers == rhs.loopAnimatedStickers
}
}
public enum ChatControllerInteractionLongTapAction { public enum ChatControllerInteractionLongTapAction {
case url(String) case url(String)
case mention(String) case mention(String)
@ -104,9 +120,10 @@ public final class ChatControllerInteraction {
var contextHighlightedState: ChatInterfaceHighlightedState? var contextHighlightedState: ChatInterfaceHighlightedState?
var automaticMediaDownloadSettings: MediaAutoDownloadSettings var automaticMediaDownloadSettings: MediaAutoDownloadSettings
var pollActionState: ChatInterfacePollActionState var pollActionState: ChatInterfacePollActionState
var stickerSettings: ChatInterfaceStickerSettings
var searchTextHighightState: String? var searchTextHighightState: String?
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) { init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage self.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention
@ -154,6 +171,7 @@ public final class ChatControllerInteraction {
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
self.pollActionState = pollActionState self.pollActionState = pollActionState
self.stickerSettings = stickerSettings
} }
static var `default`: ChatControllerInteraction { static var `default`: ChatControllerInteraction {
@ -175,6 +193,6 @@ public final class ChatControllerInteraction {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState()) pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
} }
} }

View File

@ -1497,6 +1497,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.historyNode.prefetchManager.updateAutoDownloadSettings(settings) self.historyNode.prefetchManager.updateAutoDownloadSettings(settings)
} }
func updateStickerSettings(_ settings: ChatInterfaceStickerSettings) {
self.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateStickerSettings()
}
}
}
var isInputViewFocused: Bool { var isInputViewFocused: Bool {
if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
return inputPanelNode.isFocused return inputPanelNode.isFocused

View File

@ -145,7 +145,6 @@ final class ChatMediaInputStickerGridItem: GridItem {
let node = ChatMediaInputStickerGridItemNode() let node = ChatMediaInputStickerGridItemNode()
node.interfaceInteraction = self.interfaceInteraction node.interfaceInteraction = self.interfaceInteraction
node.inputNodeInteraction = self.inputNodeInteraction node.inputNodeInteraction = self.inputNodeInteraction
node.setup(account: self.account, stickerItem: self.stickerItem)
node.selected = self.selected node.selected = self.selected
return node return node
} }
@ -157,7 +156,6 @@ final class ChatMediaInputStickerGridItem: GridItem {
} }
node.interfaceInteraction = self.interfaceInteraction node.interfaceInteraction = self.interfaceInteraction
node.inputNodeInteraction = self.inputNodeInteraction node.inputNodeInteraction = self.inputNodeInteraction
node.setup(account: self.account, stickerItem: self.stickerItem)
node.selected = self.selected node.selected = self.selected
} }
} }
@ -209,11 +207,6 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
} }
func setup(account: Account, stickerItem: StickerPackItem) {
//self.updateSelectionState(animated: false)
//self.updateHiddenMedia()
}
override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
guard let item = item as? ChatMediaInputStickerGridItem else { guard let item = item as? ChatMediaInputStickerGridItem else {
return return
@ -232,13 +225,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
self.addSubnode(animationNode) self.addSubnode(animationNode)
} }
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.account.postbox, file: item.stickerItem.file, small: false, size: CGSize(width: 160.0, height: 160.0))) self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.account.postbox, file: item.stickerItem.file, small: false, size: CGSize(width: 160.0, height: 160.0)))
if self.isPlaying { self.updateVisibility()
self.didSetUpAnimationNode = true
self.animationNode?.setup(account: item.account, resource: item.stickerItem.file.resource, width: 160, height: 160, mode: .cached)
} else {
self.didSetUpAnimationNode = false
}
self.animationNode?.visibility = self.isPlaying
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(item.stickerItem.file), resource: item.stickerItem.file.resource).start()) self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(item.stickerItem.file), resource: item.stickerItem.file.resource).start())
} else { } else {
if let animationNode = self.animationNode { if let animationNode = self.animationNode {
@ -297,7 +284,10 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
} }
func updateVisibility() { func updateVisibility() {
let isPlaying = self.isPanelVisible && self.isVisibleInGrid guard let item = self.item else {
return
}
let isPlaying = self.isPanelVisible && self.isVisibleInGrid && (item.interfaceInteraction?.stickerSettings.loopAnimatedStickers ?? true)
if self.isPlaying != isPlaying { if self.isPlaying != isPlaying {
self.isPlaying = isPlaying self.isPlaying = isPlaying
self.animationNode?.visibility = isPlaying self.animationNode?.visibility = isPlaying

View File

@ -103,7 +103,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
override var visibility: ListViewItemNodeVisibility { override var visibility: ListViewItemNodeVisibility {
didSet { didSet {
self.visibilityStatus = self.visibility != .none self.visibilityStatus = self.visibility != .none && false
} }
} }
@ -171,30 +171,30 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
self.currentThumbnailItem = thumbnailItem self.currentThumbnailItem = thumbnailItem
if let thumbnailItem = thumbnailItem { if let thumbnailItem = thumbnailItem {
switch thumbnailItem { switch thumbnailItem {
case let .still(representation): case let .still(representation):
let imageSize = representation.dimensions.aspectFitted(boundingImageSize) let imageSize = representation.dimensions.aspectFitted(boundingImageSize)
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
imageApply() imageApply()
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, representation: representation)) self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, representation: representation))
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
case let .animated(resource): case let .animated(resource):
let imageSize = boundingImageSize let imageSize = boundingImageSize
let animatedStickerNode: AnimatedStickerNode let animatedStickerNode: AnimatedStickerNode
if let current = self.animatedStickerNode { if let current = self.animatedStickerNode {
animatedStickerNode = current animatedStickerNode = current
} else { } else {
animatedStickerNode = AnimatedStickerNode() animatedStickerNode = AnimatedStickerNode()
self.animatedStickerNode = animatedStickerNode self.animatedStickerNode = animatedStickerNode
animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
self.addSubnode(animatedStickerNode) self.addSubnode(animatedStickerNode)
animatedStickerNode.setup(account: account, resource: resource, width: 80, height: 80, mode: .cached) animatedStickerNode.setup(account: account, resource: resource, width: 80, height: 80, mode: .cached)
animatedStickerNode.visibility = self.visibilityStatus animatedStickerNode.visibility = self.visibilityStatus && false
} }
if let animatedStickerNode = self.animatedStickerNode { if let animatedStickerNode = self.animatedStickerNode {
animatedStickerNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) animatedStickerNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
} }
} }
if let resourceReference = resourceReference { if let resourceReference = resourceReference {
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start()) self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start())

View File

@ -108,14 +108,14 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme) self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme)
self.badgeBackground.image = generateFilledCircleImage(diameter: 16.0, color: theme.chat.inputPanel.mediaRecordingDotColor) self.badgeBackground.image = generateFilledCircleImage(diameter: 10.0, color: theme.chat.inputPanel.mediaRecordingDotColor)
let imageSize = CGSize(width: 26.0, height: 26.0) let imageSize = CGSize(width: 26.0, height: 26.0)
let imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize) let imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
self.imageNode.frame = imageFrame self.imageNode.frame = imageFrame
if let image = self.badgeBackground.image { if let image = self.badgeBackground.image {
self.badgeBackground.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - image.size.width + 2.0, y: imageFrame.maxY - image.size.width + 3.0), size: image.size) self.badgeBackground.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - image.size.width - 1.0, y: imageFrame.maxY - image.size.width + 1.0), size: image.size)
} }
} }

View File

@ -385,7 +385,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st
} }
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .customText(text, entities): case let .customText(text, entities):
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, underlineLinks: false) attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false)
case let .botDomainAccessGranted(domain): case let .botDomainAccessGranted(domain):
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor)
case let .botSentSecureValues(types): case let .botSentSecureValues(types):

View File

@ -17,6 +17,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let imageNode: TransformImageNode let imageNode: TransformImageNode
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
private var didSetUpAnimationNode = false private var didSetUpAnimationNode = false
private var isPlaying = false
private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
private var swipeToReplyFeedback: HapticFeedback? private var swipeToReplyFeedback: HapticFeedback?
@ -90,8 +91,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
self.view.addGestureRecognizer(replyRecognizer) self.view.addGestureRecognizer(replyRecognizer)
} }
private var visibilityPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
override var visibility: ListViewItemNodeVisibility { override var visibility: ListViewItemNodeVisibility {
didSet { didSet {
let wasVisible = oldValue != .none let wasVisible = oldValue != .none
@ -106,21 +106,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var visibilityStatus: Bool = false { private var visibilityStatus: Bool = false {
didSet { didSet {
if self.visibilityStatus != oldValue { if self.visibilityStatus != oldValue {
if self.visibilityStatus { self.updateVisibility()
self.animationNode.visibility = true
self.visibilityPromise.set(true)
if let item = self.item, !self.didSetUpAnimationNode {
for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile {
self.didSetUpAnimationNode = true
self.animationNode.setup(account: item.context.account, resource: telegramFile.resource, width: 384, height: 384, mode: .cached)
}
}
}
} else {
self.animationNode.visibility = false
self.visibilityPromise.set(false)
}
} }
} }
} }
@ -133,7 +119,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if self.telegramFile?.id != telegramFile.id { if self.telegramFile?.id != telegramFile.id {
self.telegramFile = telegramFile self.telegramFile = telegramFile
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: telegramFile, small: false, size: CGSize(width: 384.0, height: 384.0), thumbnail: false)) self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: telegramFile, small: false, size: CGSize(width: 384.0, height: 384.0), thumbnail: false))
if self.visibilityStatus { self.updateVisibility()
if self.visibilityStatus && false {
self.didSetUpAnimationNode = true self.didSetUpAnimationNode = true
self.animationNode.setup(account: item.context.account, resource: telegramFile.resource, width: 384, height: 384, mode: .cached) self.animationNode.setup(account: item.context.account, resource: telegramFile.resource, width: 384, height: 384, mode: .cached)
} }
@ -144,6 +131,31 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
} }
func updateVisibility() {
guard let item = self.item else {
return
}
let isPlaying = self.visibilityStatus && item.controllerInteraction.stickerSettings.loopAnimatedStickers
if self.isPlaying != isPlaying {
self.isPlaying = isPlaying
self.animationNode.visibility = isPlaying
if let item = self.item, isPlaying, !self.didSetUpAnimationNode {
self.didSetUpAnimationNode = true
for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile {
self.animationNode.setup(account: item.context.account, resource: telegramFile.resource, width: 384, height: 384, mode: .cached)
break
}
}
}
}
}
override func updateStickerSettings() {
self.updateVisibility()
}
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, Bool) -> Void) { override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, Bool) -> Void) {
let displaySize = CGSize(width: 184.0, height: 184.0) let displaySize = CGSize(width: 184.0, height: 184.0)
let telegramFile = self.telegramFile let telegramFile = self.telegramFile

View File

@ -15,6 +15,7 @@ private let textBoldFont = Font.semibold(15.0)
private let textItalicFont = Font.italic(15.0) private let textItalicFont = Font.italic(15.0)
private let textBoldItalicFont = Font.semiboldItalic(15.0) private let textBoldItalicFont = Font.semiboldItalic(15.0)
private let textFixedFont = Font.regular(15.0) private let textFixedFont = Font.regular(15.0)
private let textBlockQuoteFont = Font.regular(15.0)
private let buttonFont = Font.semibold(13.0) private let buttonFont = Font.semibold(13.0)
enum ChatMessageAttachedContentActionIcon { enum ChatMessageAttachedContentActionIcon {
@ -371,7 +372,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
string.append(NSAttributedString(string: "\n", font: textFont, textColor: messageTheme.primaryTextColor)) string.append(NSAttributedString(string: "\n", font: textFont, textColor: messageTheme.primaryTextColor))
} }
if let entities = entities { if let entities = entities {
string.append(stringWithAppliedEntities(text, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont)) string.append(stringWithAppliedEntities(text, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont))
} else { } else {
string.append(NSAttributedString(string: text + "\n", font: textFont, textColor: messageTheme.primaryTextColor)) string.append(NSAttributedString(string: text + "\n", font: textFont, textColor: messageTheme.primaryTextColor))
} }

View File

@ -2191,7 +2191,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
} }
} }
override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? {
for contentNode in self.contentNodes { for contentNode in self.contentNodes {
if let playMediaWithSound = contentNode.playMediaWithSound() { if let playMediaWithSound = contentNode.playMediaWithSound() {

View File

@ -698,6 +698,9 @@ public class ChatMessageItemView: ListViewItemNode {
func updateAutomaticMediaDownloadSettings() { func updateAutomaticMediaDownloadSettings() {
} }
func updateStickerSettings() {
}
func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? {
return nil return nil
} }

View File

@ -81,13 +81,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
} }
if !item.message.containsSecretMedia { if !item.message.containsSecretMedia {
if telegramFile.isAnimated { if telegramFile.isAnimated && item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs {
if case .full = automaticDownload { if case .full = automaticDownload {
automaticPlayback = item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs automaticPlayback = true
} else { } else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
} }
} else if telegramFile.isVideo && item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos { } else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos {
if case .full = automaticDownload { if case .full = automaticDownload {
automaticPlayback = true automaticPlayback = true
} else { } else {

View File

@ -230,7 +230,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let forceStatusNewline = false let forceStatusNewline = false
if let entities = entities { if let entities = entities {
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont) attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont)
} else { } else {
attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor) attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor)
} }
@ -242,7 +242,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0) let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0)
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets)) let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor))
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size) var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))

View File

@ -84,6 +84,7 @@ public final class ChatPresentationData {
let messageItalicFont: UIFont let messageItalicFont: UIFont
let messageBoldItalicFont: UIFont let messageBoldItalicFont: UIFont
let messageFixedFont: UIFont let messageFixedFont: UIFont
let messageBlockQuoteFont: UIFont
init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool) { init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool) {
self.theme = theme self.theme = theme
@ -103,5 +104,6 @@ public final class ChatPresentationData {
self.messageItalicFont = UIFont.italicSystemFont(ofSize: baseFontSize) self.messageItalicFont = UIFont.italicSystemFont(ofSize: baseFontSize)
self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize) self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize)
self.messageFixedFont = UIFont(name: "Menlo-Regular", size: baseFontSize - 1.0) ?? UIFont.systemFont(ofSize: baseFontSize) self.messageFixedFont = UIFont(name: "Menlo-Regular", size: baseFontSize - 1.0) ?? UIFont.systemFont(ofSize: baseFontSize)
self.messageBlockQuoteFont = UIFont.systemFont(ofSize: baseFontSize - 1.0)
} }
} }

View File

@ -394,7 +394,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
pollActionState: ChatInterfacePollActionState()) pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
self.listNode.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in self.listNode.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in

View File

@ -39,6 +39,7 @@ struct ChatTextFontAttributes: OptionSet {
static let bold = ChatTextFontAttributes(rawValue: 1 << 0) static let bold = ChatTextFontAttributes(rawValue: 1 << 0)
static let italic = ChatTextFontAttributes(rawValue: 1 << 1) static let italic = ChatTextFontAttributes(rawValue: 1 << 1)
static let monospace = ChatTextFontAttributes(rawValue: 1 << 2) static let monospace = ChatTextFontAttributes(rawValue: 1 << 2)
static let blockQuote = ChatTextFontAttributes(rawValue: 1 << 3)
} }
func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor) -> NSAttributedString { func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor) -> NSAttributedString {

View File

@ -5,6 +5,7 @@ import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences
private final class FeaturedStickerPacksControllerArguments { private final class FeaturedStickerPacksControllerArguments {
let account: Account let account: Account
@ -46,7 +47,7 @@ private enum FeaturedStickerPacksEntryId: Hashable {
} }
private enum FeaturedStickerPacksEntry: ItemListNodeEntry { private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, Bool, StickerPackItem?, String, Bool) case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, Bool, StickerPackItem?, String, Bool, Bool)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -57,15 +58,15 @@ private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
var stableId: FeaturedStickerPacksEntryId { var stableId: FeaturedStickerPacksEntryId {
switch self { switch self {
case let .pack(_, _, _, info, _, _, _, _): case let .pack(_, _, _, info, _, _, _, _, _):
return .pack(info.id) return .pack(info.id)
} }
} }
static func ==(lhs: FeaturedStickerPacksEntry, rhs: FeaturedStickerPacksEntry) -> Bool { static func ==(lhs: FeaturedStickerPacksEntry, rhs: FeaturedStickerPacksEntry) -> Bool {
switch lhs { switch lhs {
case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsUnread, lhsTopItem, lhsCount, lhsInstalled): case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsUnread, lhsTopItem, lhsCount, lhsPlayAnimatedStickers, lhsInstalled):
if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsUnread, rhsTopItem, rhsCount, rhsInstalled) = rhs { if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsUnread, rhsTopItem, rhsCount, rhsPlayAnimatedStickers, rhsInstalled) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -87,6 +88,9 @@ private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
if lhsCount != rhsCount { if lhsCount != rhsCount {
return false return false
} }
if lhsPlayAnimatedStickers != rhsPlayAnimatedStickers {
return false
}
if lhsInstalled != rhsInstalled { if lhsInstalled != rhsInstalled {
return false return false
} }
@ -99,9 +103,9 @@ private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
static func <(lhs: FeaturedStickerPacksEntry, rhs: FeaturedStickerPacksEntry) -> Bool { static func <(lhs: FeaturedStickerPacksEntry, rhs: FeaturedStickerPacksEntry) -> Bool {
switch lhs { switch lhs {
case let .pack(lhsIndex, _, _, _, _, _, _, _): case let .pack(lhsIndex, _, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case let .pack(rhsIndex, _, _, _, _, _, _, _): case let .pack(rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
} }
} }
@ -109,8 +113,8 @@ private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
func item(_ arguments: FeaturedStickerPacksControllerArguments) -> ListViewItem { func item(_ arguments: FeaturedStickerPacksControllerArguments) -> ListViewItem {
switch self { switch self {
case let .pack(_, theme, strings, info, unread, topItem, count, installed): case let .pack(_, theme, strings, info, unread, topItem, count, playAnimatedStickers, installed):
return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false), enabled: true, sectionId: self.section, action: { return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info) arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { _, _ in }, setPackIdWithRevealedOptions: { _, _ in
}, addPack: { }, addPack: {
@ -130,7 +134,7 @@ private struct FeaturedStickerPacksControllerState: Equatable {
} }
} }
private func featuredStickerPacksControllerEntries(presentationData: PresentationData, state: FeaturedStickerPacksControllerState, view: CombinedView, featured: [FeaturedStickerPackItem], unreadPacks: [ItemCollectionId: Bool]) -> [FeaturedStickerPacksEntry] { private func featuredStickerPacksControllerEntries(presentationData: PresentationData, state: FeaturedStickerPacksControllerState, view: CombinedView, featured: [FeaturedStickerPackItem], unreadPacks: [ItemCollectionId: Bool], stickerSettings: StickerSettings) -> [FeaturedStickerPacksEntry] {
var entries: [FeaturedStickerPacksEntry] = [] var entries: [FeaturedStickerPacksEntry] = []
if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView, !featured.isEmpty { if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView, !featured.isEmpty {
@ -145,7 +149,7 @@ private func featuredStickerPacksControllerEntries(presentationData: Presentatio
if let value = unreadPacks[item.info.id] { if let value = unreadPacks[item.info.id] {
unread = value unread = value
} }
entries.append(.pack(index, presentationData.theme, presentationData.strings, item.info, unread, item.topItems.first, presentationData.strings.StickerPack_StickerCount(item.info.count), installedPacks.contains(item.info.id))) entries.append(.pack(index, presentationData.theme, presentationData.strings, item.info, unread, item.topItems.first, presentationData.strings.StickerPack_StickerCount(item.info.count), stickerSettings.loopAnimatedStickers, installedPacks.contains(item.info.id)))
index += 1 index += 1
} }
} }
@ -195,9 +199,14 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
var previousPackCount: Int? var previousPackCount: Int?
var initialUnreadPacks: [ItemCollectionId: Bool] = [:] var initialUnreadPacks: [ItemCollectionId: Bool] = [:]
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, featured -> (ItemListControllerState, (ItemListNodeState<FeaturedStickerPacksEntry>, FeaturedStickerPacksEntry.ItemGenerationArguments)) in |> map { presentationData, state, view, featured, sharedData -> (ItemListControllerState, (ItemListNodeState<FeaturedStickerPacksEntry>, FeaturedStickerPacksEntry.ItemGenerationArguments)) in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value
}
let packCount: Int? = featured.count let packCount: Int? = featured.count
for item in featured { for item in featured {
@ -212,7 +221,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.FeaturedStickerPacks_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.FeaturedStickerPacks_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: featuredStickerPacksControllerEntries(presentationData: presentationData, state: state, view: view, featured: featured, unreadPacks: initialUnreadPacks), style: .blocks, animateChanges: false) let listState = ItemListNodeState(entries: featuredStickerPacksControllerEntries(presentationData: presentationData, state: state, view: view, featured: featured, unreadPacks: initialUnreadPacks, stickerSettings: stickerSettings), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {
actionsDisposable.dispose() actionsDisposable.dispose()
@ -226,7 +235,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
var unreadIds: [ItemCollectionId] = [] var unreadIds: [ItemCollectionId] = []
for entry in entries { for entry in entries {
switch entry { switch entry {
case let .pack(_, _, _, info, unread, _, _, _): case let .pack(_, _, _, info, unread, _, _, _, _):
if unread && !alreadyReadIds.contains(info.id) { if unread && !alreadyReadIds.contains(info.id) {
unreadIds.append(info.id) unreadIds.append(info.id)
} }

View File

@ -122,7 +122,7 @@ private let boldItalicFont = Font.semiboldItalic(16.0)
private let fixedFont = UIFont(name: "Menlo-Regular", size: 15.0) ?? textFont private let fixedFont = UIFont(name: "Menlo-Regular", size: 15.0) ?? textFont
func galleryCaptionStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> NSAttributedString { func galleryCaptionStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> NSAttributedString {
return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: UIColor(rgb: 0x5ac8fa), baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, underlineLinks: false) return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: UIColor(rgb: 0x5ac8fa), baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: textFont, underlineLinks: false)
} }
private func galleryMessageCaptionText(_ message: Message) -> String { private func galleryMessageCaptionText(_ message: Message) -> String {

View File

@ -5,6 +5,7 @@ import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences
private final class GroupStickerPackSetupControllerArguments { private final class GroupStickerPackSetupControllerArguments {
let account: Account let account: Account
@ -64,7 +65,7 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
case currentPack(Int32, PresentationTheme, PresentationStrings, GroupStickerPackCurrentItemContent) case currentPack(Int32, PresentationTheme, PresentationStrings, GroupStickerPackCurrentItemContent)
case searchInfo(PresentationTheme, String) case searchInfo(PresentationTheme, String)
case packsTitle(PresentationTheme, String) case packsTitle(PresentationTheme, String)
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool) case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, Bool)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -85,7 +86,7 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
return .index(2) return .index(2)
case .packsTitle: case .packsTitle:
return .index(3) return .index(3)
case let .pack(_, _, _, info, _, _, _): case let .pack(_, _, _, info, _, _, _, _):
return .pack(info.id) return .pack(info.id)
} }
} }
@ -128,8 +129,8 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsTopItem, lhsCount, lhsSelected): case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsTopItem, lhsCount, lhsPlayAnimatedStickers, lhsSelected):
if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsTopItem, rhsCount, rhsSelected) = rhs { if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsTopItem, rhsCount, rhsPlayAnimatedStickers, rhsSelected) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -148,6 +149,9 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
if lhsCount != rhsCount { if lhsCount != rhsCount {
return false return false
} }
if lhsPlayAnimatedStickers != rhsPlayAnimatedStickers {
return false
}
if lhsSelected != rhsSelected { if lhsSelected != rhsSelected {
return false return false
} }
@ -188,9 +192,9 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
default: default:
return true return true
} }
case let .pack(lhsIndex, _, _, _, _, _, _): case let .pack(lhsIndex, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case let .pack(rhsIndex, _, _, _, _, _, _): case let .pack(rhsIndex, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
default: default:
return false return false
@ -216,8 +220,8 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section, linkAction: nil) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section, linkAction: nil)
case let .packsTitle(theme, text): case let .packsTitle(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .pack(_, theme, strings, info, topItem, count, selected): case let .pack(_, theme, strings, info, topItem, count, playAnimatedStickers, selected):
return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: selected ? .selection : .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false), enabled: true, sectionId: self.section, action: { return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: selected ? .selection : .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: {
if selected { if selected {
arguments.openStickerPack(info) arguments.openStickerPack(info)
} else { } else {
@ -258,7 +262,7 @@ private struct GroupStickerPackSetupControllerState: Equatable {
var isSaving: Bool var isSaving: Bool
} }
private func groupStickerPackSetupControllerEntries(presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState) -> [GroupStickerPackEntry] { private func groupStickerPackSetupControllerEntries(presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings) -> [GroupStickerPackEntry] {
if initialData == nil { if initialData == nil {
return [] return []
} }
@ -288,7 +292,7 @@ private func groupStickerPackSetupControllerEntries(presentationData: Presentati
if case let .found(found) = searchState { if case let .found(found) = searchState {
selected = found.info.id == info.id selected = found.info.id == info.id
} }
entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count), selected)) entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count), stickerSettings.loopAnimatedStickers, selected))
index += 1 index += 1
} }
} }
@ -400,66 +404,71 @@ public func groupStickerPackSetupController(context: AccountContext, peerId: Pee
let previousHadData = Atomic<Bool>(value: false) let previousHadData = Atomic<Bool>(value: false)
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, initialData.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, searchState.get() |> deliverOnMainQueue) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, initialData.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, searchState.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue)
|> map { presentationData, state, initialData, view, searchState -> (ItemListControllerState, (ItemListNodeState<GroupStickerPackEntry>, GroupStickerPackEntry.ItemGenerationArguments)) in |> map { presentationData, state, initialData, view, searchState, sharedData -> (ItemListControllerState, (ItemListNodeState<GroupStickerPackEntry>, GroupStickerPackEntry.ItemGenerationArguments)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { var stickerSettings = StickerSettings.defaultSettings
dismissImpl?() if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
}) stickerSettings = value
}
var rightNavigationButton: ItemListNavigationButton?
if initialData != nil { let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
if state.isSaving { dismissImpl?()
rightNavigationButton = ItemListNavigationButton(content: .text(""), style: .activity, enabled: true, action: {}) })
} else {
let enabled: Bool var rightNavigationButton: ItemListNavigationButton?
var info: StickerPackCollectionInfo? if initialData != nil {
switch searchState.1 { if state.isSaving {
case .searching, .notFound: rightNavigationButton = ItemListNavigationButton(content: .text(""), style: .activity, enabled: true, action: {})
enabled = false } else {
case .none: let enabled: Bool
enabled = true var info: StickerPackCollectionInfo?
case let .found(data): switch searchState.1 {
enabled = true case .searching, .notFound:
info = data.info enabled = false
} case .none:
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { enabled = true
if info?.id == currentPackInfo?.id { case let .found(data):
dismissImpl?() enabled = true
} else { info = data.info
}
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: {
if info?.id == currentPackInfo?.id {
dismissImpl?()
} else {
updateState { state in
var state = state
state.isSaving = true
return state
}
saveDisposable.set((updateGroupSpecificStickerset(postbox: context.account.postbox, network: context.account.network, peerId: peerId, info: info)
|> deliverOnMainQueue).start(error: { _ in
updateState { state in updateState { state in
var state = state var state = state
state.isSaving = true state.isSaving = false
return state return state
} }
saveDisposable.set((updateGroupSpecificStickerset(postbox: context.account.postbox, network: context.account.network, peerId: peerId, info: info) }, completed: {
|> deliverOnMainQueue).start(error: { _ in dismissImpl?()
updateState { state in }))
var state = state }
state.isSaving = false })
return state
}
}, completed: {
dismissImpl?()
}))
}
})
}
} }
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Channel_Info_Stickers), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Channel_Info_Stickers), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let hasData = initialData != nil
let hadData = previousHadData.swap(hasData) let hasData = initialData != nil
let hadData = previousHadData.swap(hasData)
var emptyStateItem: ItemListLoadingIndicatorEmptyStateItem?
if !hasData { var emptyStateItem: ItemListLoadingIndicatorEmptyStateItem?
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) if !hasData {
} emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
let listState = ItemListNodeState(entries: groupStickerPackSetupControllerEntries(presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: hasData && hadData)
return (controllerState, (listState, arguments)) let listState = ItemListNodeState(entries: groupStickerPackSetupControllerEntries(presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: hasData && hadData)
} |> afterDisposed { return (controllerState, (listState, arguments))
actionsDisposable.dispose() } |> afterDisposed {
actionsDisposable.dispose()
} }
let controller = ItemListController(context: context, state: signal) let controller = ItemListController(context: context, state: signal)

View File

@ -209,7 +209,7 @@ func fetchICloudFileResource(resource: ICloudFileResource) -> Signal<MediaResour
if resource.thumbnail { if resource.thumbnail {
let tempFile = TempBox.shared.tempFile(fileName: "thumb.jpg") let tempFile = TempBox.shared.tempFile(fileName: "thumb.jpg")
var data = Data() var data = Data()
if let image = generatePdfPreviewImage(url: url, size: CGSize(width: 320.0, height: 320.0)), let jpegData = UIImageJPEGRepresentation(image, 0.5) { if let image = generatePdfPreviewImage(url: url, size: CGSize(width: 256, height: 256.0)), let jpegData = UIImageJPEGRepresentation(image, 0.5) {
data = jpegData data = jpegData
} }
if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path)) { if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path)) {

View File

@ -18,8 +18,9 @@ private final class InstalledStickerPacksControllerArguments {
let openFeatured: () -> Void let openFeatured: () -> Void
let openArchived: ([ArchivedStickerPackItem]?) -> Void let openArchived: ([ArchivedStickerPackItem]?) -> Void
let openSuggestOptions: () -> Void let openSuggestOptions: () -> Void
let toggleAnimatedStickers: (Bool) -> Void
init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ArchivedStickerPackItem) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void) { init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ArchivedStickerPackItem) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void, toggleAnimatedStickers: @escaping (Bool) -> Void) {
self.account = account self.account = account
self.openStickerPack = openStickerPack self.openStickerPack = openStickerPack
self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions
@ -29,6 +30,7 @@ private final class InstalledStickerPacksControllerArguments {
self.openFeatured = openFeatured self.openFeatured = openFeatured
self.openArchived = openArchived self.openArchived = openArchived
self.openSuggestOptions = openSuggestOptions self.openSuggestOptions = openSuggestOptions
self.toggleAnimatedStickers = toggleAnimatedStickers
} }
} }
@ -85,13 +87,15 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
case trending(PresentationTheme, String, Int32) case trending(PresentationTheme, String, Int32)
case archived(PresentationTheme, String, Int32, [ArchivedStickerPackItem]?) case archived(PresentationTheme, String, Int32, [ArchivedStickerPackItem]?)
case masks(PresentationTheme, String) case masks(PresentationTheme, String)
case animatedStickers(PresentationTheme, String, Bool)
case animatedStickersInfo(PresentationTheme, String)
case packsTitle(PresentationTheme, String) case packsTitle(PresentationTheme, String)
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, ItemListStickerPackItemEditing) case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, Bool, ItemListStickerPackItemEditing)
case packsInfo(PresentationTheme, String) case packsInfo(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .suggestOptions, .trending, .masks, .archived: case .suggestOptions, .trending, .masks, .archived, .animatedStickers, .animatedStickersInfo:
return InstalledStickerPacksSection.service.rawValue return InstalledStickerPacksSection.service.rawValue
case .packsTitle, .pack, .packsInfo: case .packsTitle, .pack, .packsInfo:
return InstalledStickerPacksSection.stickers.rawValue return InstalledStickerPacksSection.stickers.rawValue
@ -108,12 +112,16 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
return .index(2) return .index(2)
case .masks: case .masks:
return .index(3) return .index(3)
case .packsTitle: case .animatedStickers:
return .index(4) return .index(4)
case let .pack(_, _, _, info, _, _, _, _): case .animatedStickersInfo:
return .index(5)
case .packsTitle:
return .index(6)
case let .pack(_, _, _, info, _, _, _, _, _):
return .pack(info.id) return .pack(info.id)
case .packsInfo: case .packsInfo:
return .index(5) return .index(7)
} }
} }
@ -143,14 +151,26 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .animatedStickers(lhsTheme, lhsText, lhsValue):
if case let .animatedStickers(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .animatedStickersInfo(lhsTheme, lhsText):
if case let .animatedStickersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .packsTitle(lhsTheme, lhsText): case let .packsTitle(lhsTheme, lhsText):
if case let .packsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .packsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
} }
case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsTopItem, lhsCount, lhsEnabled, lhsEditing): case let .pack(lhsIndex, lhsTheme, lhsStrings, lhsInfo, lhsTopItem, lhsCount, lhsAnimatedStickers, lhsEnabled, lhsEditing):
if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsTopItem, rhsCount, rhsEnabled, rhsEditing) = rhs { if case let .pack(rhsIndex, rhsTheme, rhsStrings, rhsInfo, rhsTopItem, rhsCount, rhsAnimatedStickers, rhsEnabled, rhsEditing) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -169,6 +189,9 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
if lhsCount != rhsCount { if lhsCount != rhsCount {
return false return false
} }
if lhsAnimatedStickers != rhsAnimatedStickers {
return false
}
if lhsEnabled != rhsEnabled { if lhsEnabled != rhsEnabled {
return false return false
} }
@ -218,16 +241,30 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
default: default:
return true return true
} }
case .packsTitle: case .animatedStickers:
switch rhs { switch rhs {
case .suggestOptions, .trending, .masks, .archived, .packsTitle: case .suggestOptions, .trending, .archived, .masks, .animatedStickers:
return false return false
default: default:
return true return true
} }
case let .pack(lhsIndex, _, _, _, _, _, _, _): case .animatedStickersInfo:
switch rhs { switch rhs {
case let .pack(rhsIndex, _, _, _, _, _, _, _): case .suggestOptions, .trending, .archived, .masks, .animatedStickers, .animatedStickersInfo:
return false
default:
return true
}
case .packsTitle:
switch rhs {
case .suggestOptions, .trending, .masks, .archived, .animatedStickers, .animatedStickersInfo, .packsTitle:
return false
default:
return true
}
case let .pack(lhsIndex, _, _, _, _, _, _, _, _):
switch rhs {
case let .pack(rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
case .packsInfo: case .packsInfo:
return true return true
@ -262,10 +299,16 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: {
arguments.openArchived(archived) arguments.openArchived(archived)
}) })
case let .animatedStickers(theme, text, value):
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleAnimatedStickers(value)
})
case let .animatedStickersInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .packsTitle(theme, text): case let .packsTitle(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .pack(_, theme, strings, info, topItem, count, enabled, editing): case let .pack(_, theme, strings, info, topItem, count, animatedStickers, enabled, editing):
return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, sectionId: self.section, action: { return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info) arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { current, previous in }, setPackIdWithRevealedOptions: { current, previous in
arguments.setPackIdWithRevealedOptions(current, previous) arguments.setPackIdWithRevealedOptions(current, previous)
@ -353,6 +396,10 @@ private func installedStickerPacksControllerEntries(presentationData: Presentati
entries.append(.archived(presentationData.theme, presentationData.strings.StickerPacksSettings_ArchivedPacks, Int32(archived.count), archived)) entries.append(.archived(presentationData.theme, presentationData.strings.StickerPacksSettings_ArchivedPacks, Int32(archived.count), archived))
} }
entries.append(.masks(presentationData.theme, presentationData.strings.MaskStickerSettings_Title)) entries.append(.masks(presentationData.theme, presentationData.strings.MaskStickerSettings_Title))
entries.append(.animatedStickers(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickers, stickerSettings.loopAnimatedStickers))
entries.append(.animatedStickersInfo(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickersInfo))
entries.append(.packsTitle(presentationData.theme, presentationData.strings.StickerPacksSettings_StickerPacksSection)) entries.append(.packsTitle(presentationData.theme, presentationData.strings.StickerPacksSettings_StickerPacksSection))
case .masks: case .masks:
if let archived = archived, !archived.isEmpty { if let archived = archived, !archived.isEmpty {
@ -365,7 +412,7 @@ private func installedStickerPacksControllerEntries(presentationData: Presentati
var index: Int32 = 0 var index: Int32 = 0
for entry in packsEntries { for entry in packsEntries {
if let info = entry.info as? StickerPackCollectionInfo { if let info = entry.info as? StickerPackCollectionInfo {
entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count), true, ItemListStickerPackItemEditing(editable: true, editing: state.editing, revealed: state.packIdWithRevealedOptions == entry.id, reorderable: true))) entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count), stickerSettings.loopAnimatedStickers, true, ItemListStickerPackItemEditing(editable: true, editing: state.editing, revealed: state.packIdWithRevealedOptions == entry.id, reorderable: true)))
index += 1 index += 1
} }
} }
@ -505,18 +552,10 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
]) ])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
/* }, toggleAnimatedStickers: { value in
let suggestString: String let _ = updateStickerSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
switch stickerSettings.emojiStickerSuggestionMode { return current.withUpdatedLoopAnimatedStickers(value)
case .none: }).start()
suggestString = presentationData.strings.Stickers_SuggestNone
case .all:
case .installed:
suggestString = presentationData.strings.Stickers_SuggestAdded
}
*/
}) })
let stickerPacks = Promise<CombinedView>() let stickerPacks = Promise<CombinedView>()
stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])])) stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])]))
@ -531,10 +570,8 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
featured.set(.single([])) featured.set(.single([]))
archivedPromise.set(.single(nil) |> then(archivedStickerPacks(account: context.account, namespace: .masks) |> map(Optional.init))) archivedPromise.set(.single(nil) |> then(archivedStickerPacks(account: context.account, namespace: .masks) |> map(Optional.init)))
} }
var previousPackCount: Int? var previousPackCount: Int?
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), stickerPacks.get(), combineLatest(queue: .mainQueue(), featured.get(), archivedPromise.get()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings])) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), stickerPacks.get(), combineLatest(queue: .mainQueue(), featured.get(), archivedPromise.get()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]))
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, featuredAndArchived, sharedData -> (ItemListControllerState, (ItemListNodeState<InstalledStickerPacksEntry>, InstalledStickerPacksEntry.ItemGenerationArguments)) in |> map { presentationData, state, view, featuredAndArchived, sharedData -> (ItemListControllerState, (ItemListNodeState<InstalledStickerPacksEntry>, InstalledStickerPacksEntry.ItemGenerationArguments)) in
@ -549,12 +586,6 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
let leftNavigationButton: ItemListNavigationButton? = nil let leftNavigationButton: ItemListNavigationButton? = nil
/*if case .modal = mode {
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
}*/
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
if let packCount = packCount, packCount != 0 { if let packCount = packCount, packCount != 0 {
if case .modal = mode { if case .modal = mode {
@ -603,7 +634,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
controller.reorderEntry = { fromIndex, toIndex, entries in controller.reorderEntry = { fromIndex, toIndex, entries in
let fromEntry = entries[fromIndex] let fromEntry = entries[fromIndex]
guard case let .pack(_, _, _, fromPackInfo, _, _, _, _) = fromEntry else { guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else {
return return
} }
var referenceId: ItemCollectionId? var referenceId: ItemCollectionId?
@ -611,7 +642,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
var afterAll = false var afterAll = false
if toIndex < entries.count { if toIndex < entries.count {
switch entries[toIndex] { switch entries[toIndex] {
case let .pack(_, _, _, toPackInfo, _, _, _, _): case let .pack(_, _, _, toPackInfo, _, _, _, _, _):
referenceId = toPackInfo.id referenceId = toPackInfo.id
default: default:
if entries[toIndex] < fromEntry { if entries[toIndex] < fromEntry {

View File

@ -182,7 +182,7 @@ class ItemListAddressItemNode: ListViewItemNode {
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let baseColor = item.theme.list.itemPrimaryTextColor let baseColor = item.theme.list.itemPrimaryTextColor
let string = stringWithAppliedEntities(item.text, entities: [], baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) let string = stringWithAppliedEntities(item.text, entities: [], baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textFont)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset - 98.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset - 98.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0 let padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0

View File

@ -56,7 +56,7 @@ private struct ItemListNodeTransition<Entry: ItemListNodeEntry> {
let animateAlpha: Bool let animateAlpha: Bool
let crossfade: Bool let crossfade: Bool
let mergedEntries: [Entry] let mergedEntries: [Entry]
let userInteractionEnabled: Bool let scrollEnabled: Bool
} }
struct ItemListNodeState<Entry: ItemListNodeEntry> { struct ItemListNodeState<Entry: ItemListNodeEntry> {
@ -66,12 +66,12 @@ struct ItemListNodeState<Entry: ItemListNodeEntry> {
let searchItem: ItemListControllerSearch? let searchItem: ItemListControllerSearch?
let animateChanges: Bool let animateChanges: Bool
let crossfadeState: Bool let crossfadeState: Bool
let userInteractionEnabled: Bool let scrollEnabled: Bool
let focusItemTag: ItemListItemTag? let focusItemTag: ItemListItemTag?
let ensureVisibleItemTag: ItemListItemTag? let ensureVisibleItemTag: ItemListItemTag?
let initialScrollToItem: ListViewScrollToItem? let initialScrollToItem: ListViewScrollToItem?
init(entries: [Entry], style: ItemListStyle, focusItemTag: ItemListItemTag? = nil, ensureVisibleItemTag: ItemListItemTag? = nil, emptyStateItem: ItemListControllerEmptyStateItem? = nil, searchItem: ItemListControllerSearch? = nil, initialScrollToItem: ListViewScrollToItem? = nil, crossfadeState: Bool = false, animateChanges: Bool = true, userInteractionEnabled: Bool = true) { init(entries: [Entry], style: ItemListStyle, focusItemTag: ItemListItemTag? = nil, ensureVisibleItemTag: ItemListItemTag? = nil, emptyStateItem: ItemListControllerEmptyStateItem? = nil, searchItem: ItemListControllerSearch? = nil, initialScrollToItem: ListViewScrollToItem? = nil, crossfadeState: Bool = false, animateChanges: Bool = true, scrollEnabled: Bool = true) {
self.entries = entries self.entries = entries
self.style = style self.style = style
self.emptyStateItem = emptyStateItem self.emptyStateItem = emptyStateItem
@ -81,7 +81,7 @@ struct ItemListNodeState<Entry: ItemListNodeEntry> {
self.focusItemTag = focusItemTag self.focusItemTag = focusItemTag
self.ensureVisibleItemTag = ensureVisibleItemTag self.ensureVisibleItemTag = ensureVisibleItemTag
self.initialScrollToItem = initialScrollToItem self.initialScrollToItem = initialScrollToItem
self.userInteractionEnabled = userInteractionEnabled self.scrollEnabled = scrollEnabled
} }
} }
@ -275,7 +275,7 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UIScrollV
scrollToItem = state.initialScrollToItem scrollToItem = state.initialScrollToItem
} }
return ItemListNodeTransition(theme: theme, entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, searchItem: state.searchItem, focusItemTag: state.focusItemTag, ensureVisibleItemTag: state.ensureVisibleItemTag, scrollToItem: scrollToItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && state.animateChanges, crossfade: state.crossfadeState, mergedEntries: state.entries, userInteractionEnabled: state.userInteractionEnabled) return ItemListNodeTransition(theme: theme, entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, searchItem: state.searchItem, focusItemTag: state.focusItemTag, ensureVisibleItemTag: state.ensureVisibleItemTag, scrollToItem: scrollToItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && state.animateChanges, crossfade: state.crossfadeState, mergedEntries: state.entries, scrollEnabled: state.scrollEnabled)
}) |> deliverOnMainQueue).start(next: { [weak self] transition in }) |> deliverOnMainQueue).start(next: { [weak self] transition in
if let strongSelf = self { if let strongSelf = self {
strongSelf.enqueueTransition(transition) strongSelf.enqueueTransition(transition)
@ -593,7 +593,7 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UIScrollV
self.emptyStateNode = nil self.emptyStateNode = nil
} }
} }
self.listNode.isUserInteractionEnabled = transition.userInteractionEnabled self.listNode.scrollEnabled = transition.scrollEnabled
if updateSearchItem { if updateSearchItem {
self.requestLayout?(.animated(duration: 0.3, curve: .spring)) self.requestLayout?(.animated(duration: 0.3, curve: .spring))

View File

@ -185,7 +185,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode {
} }
let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes) let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes)
let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont) let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: titleFont)
let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -47,13 +47,14 @@ final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let control: ItemListStickerPackItemControl let control: ItemListStickerPackItemControl
let editing: ItemListStickerPackItemEditing let editing: ItemListStickerPackItemEditing
let enabled: Bool let enabled: Bool
let playAnimatedStickers: Bool
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let action: (() -> Void)? let action: (() -> Void)?
let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void
let addPack: () -> Void let addPack: () -> Void
let removePack: () -> Void let removePack: () -> Void
init(theme: PresentationTheme, strings: PresentationStrings, account: Account, packInfo: StickerPackCollectionInfo, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void) { init(theme: PresentationTheme, strings: PresentationStrings, account: Account, packInfo: StickerPackCollectionInfo, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.account = account self.account = account
@ -64,6 +65,7 @@ final class ItemListStickerPackItem: ListViewItem, ItemListItem {
self.control = control self.control = control
self.editing = editing self.editing = editing
self.enabled = enabled self.enabled = enabled
self.playAnimatedStickers = playAnimatedStickers
self.sectionId = sectionId self.sectionId = sectionId
self.action = action self.action = action
self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions
@ -162,7 +164,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
let isVisible = self.visibility != .none let isVisible = self.visibility != .none
if wasVisible != isVisible { if wasVisible != isVisible {
self.animationNode?.visibility = isVisible self.animationNode?.visibility = isVisible && (self.layoutParams?.0.playAnimatedStickers ?? true)
} }
} }
} }
@ -558,8 +560,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
strongSelf.animationNode = animationNode strongSelf.animationNode = animationNode
strongSelf.addSubnode(animationNode) strongSelf.addSubnode(animationNode)
animationNode.setup(account: item.account, resource: resource, width: 80, height: 80, mode: .cached) animationNode.setup(account: item.account, resource: resource, width: 80, height: 80, mode: .cached)
animationNode.visibility = strongSelf.visibility != .none
} }
animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers
if let animationNode = strongSelf.animationNode { if let animationNode = strongSelf.animationNode {
transition.updateFrame(node: animationNode, frame: imageFrame) transition.updateFrame(node: animationNode, frame: imageFrame)
} }

View File

@ -209,7 +209,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode {
case .highlighted: case .highlighted:
baseColor = item.theme.list.itemHighlightedColor baseColor = item.theme.list.itemHighlightedColor
} }
let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textFont)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: item.multiline ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: item.multiline ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize = CGSize(width: params.width, height: textLayout.size.height + 39.0) let contentSize = CGSize(width: params.width, height: textLayout.size.height + 39.0)

View File

@ -38,6 +38,11 @@ public func navigateToChatController(navigationController: NavigationController,
if activateInput { if activateInput {
controller.activateInput() controller.activateInput()
} }
if let botStart = botStart {
controller.updateChatPresentationInterfaceState(interactive: false) { state -> ChatPresentationInterfaceState in
return state.updatedBotStartPayload(botStart.payload)
}
}
found = true found = true
break break
} }
@ -48,6 +53,11 @@ public func navigateToChatController(navigationController: NavigationController,
let controller: ChatController let controller: ChatController
if let chatController = chatController { if let chatController = chatController {
controller = chatController controller = chatController
if let botStart = botStart {
controller.updateChatPresentationInterfaceState(interactive: false) { state -> ChatPresentationInterfaceState in
return state.updatedBotStartPayload(botStart.payload)
}
}
} else { } else {
controller = ChatController(context: context, chatLocation: chatLocation, messageId: messageId, botStart: botStart) controller = ChatController(context: context, chatLocation: chatLocation, messageId: messageId, botStart: botStart)
} }

View File

@ -192,6 +192,20 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
return .openUrl(url: "yandexnavi://build_route_on_map?lat_to=\(lat)&lon_to=\(lon)") return .openUrl(url: "yandexnavi://build_route_on_map?lat_to=\(lat)&lon_to=\(lon)")
})) }))
} }
options.append(OpenInOption(application: .other(title: "Moovit", identifier: 498477945, scheme: "moovit", store: nil), action: {
if withDirections {
let destName: String
if let title = location.venue?.title.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed), title.count > 0 {
destName = title
} else {
destName = ""
}
return .openUrl(url: "moovit://directions?dest_lat=\(lat)&dest_lon=\(lon)&dest_name=\(destName)&partner_id=Telegram")
} else {
return .openUrl(url: "moovit://nearby?lat=\(lat)&lon=\(lon)&partner_id=Telegram")
}
}))
if !withDirections { if !withDirections {
options.append(OpenInOption(application: .other(title: "HERE Maps", identifier: 955837609, scheme: "here-location", store: nil), action: { options.append(OpenInOption(application: .other(title: "HERE Maps", identifier: 955837609, scheme: "here-location", store: nil), action: {

View File

@ -101,7 +101,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
}, seekToTimecode: { _, _, _ in }, seekToTimecode: { _, _, _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState()) }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)

View File

@ -3,7 +3,13 @@ import UIKit
import Display import Display
func generatePdfPreviewImage(url: URL, size: CGSize) -> UIImage? { func generatePdfPreviewImage(url: URL, size: CGSize) -> UIImage? {
guard let document = CGPDFDocument(url as CFURL) else { return nil } guard let data = try? Data(contentsOf: url, options: .mappedIfSafe) else { return nil }
return generatePdfPreviewImage(data: data, size: size)
}
func generatePdfPreviewImage(data: Data, size: CGSize) -> UIImage? {
guard let provider = CGDataProvider(data: data as CFData) else { return nil }
guard let document = CGPDFDocument(provider) else { return nil }
guard let firstPage = document.page(at: 1) else { return nil } guard let firstPage = document.page(at: 1) else { return nil }
let context = DrawingContext(size: size) let context = DrawingContext(size: size)

View File

@ -160,7 +160,7 @@ final public class PasscodeEntryController: ViewController {
}).start() }).start()
} }
let isMainApp = !strongSelf.context.sharedContext.applicationBindings.isMainApp let isMainApp = strongSelf.context.sharedContext.applicationBindings.isMainApp
let _ = updatePresentationPasscodeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { settings in let _ = updatePresentationPasscodeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { settings in
if isMainApp { if isMainApp {
return settings.withUpdatedBiometricsDomainState(LocalAuth.evaluatedPolicyDomainState) return settings.withUpdatedBiometricsDomainState(LocalAuth.evaluatedPolicyDomainState)

View File

@ -5,7 +5,7 @@ import TelegramCore
import MobileCoreServices import MobileCoreServices
private func rtfStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> String { private func rtfStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> String {
let test = stringWithAppliedEntities(text, entities: entities, baseColor: .black, linkColor: .black, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.semibold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0), underlineLinks: false, external: true) let test = stringWithAppliedEntities(text, entities: entities, baseColor: .black, linkColor: .black, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.semibold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0), blockQuoteFont: Font.regular(14.0), underlineLinks: false, external: true)
if let data = try? test.data(from: NSRange(location: 0, length: test.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf]) { if let data = try? test.data(from: NSRange(location: 0, length: test.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf]) {
if var rtf = String(data: data, encoding: .windowsCP1252) { if var rtf = String(data: data, encoding: .windowsCP1252) {

View File

@ -272,7 +272,7 @@ public class PeerMediaCollectionController: TelegramController {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState()) pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction

View File

@ -7,4 +7,5 @@ public extension PresentationSurfaceLevel {
static let overlayMedia = PresentationSurfaceLevel(rawValue: 2) static let overlayMedia = PresentationSurfaceLevel(rawValue: 2)
static let notifications = PresentationSurfaceLevel(rawValue: 3) static let notifications = PresentationSurfaceLevel(rawValue: 3)
static let passcode = PresentationSurfaceLevel(rawValue: 4) static let passcode = PresentationSurfaceLevel(rawValue: 4)
static let update = PresentationSurfaceLevel(rawValue: 5)
} }

View File

@ -634,7 +634,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
} }
let controllerState = ItemListControllerState(theme: presentationData.theme, title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(theme: presentationData.theme, title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: entries, style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: crossfadeState, animateChanges: animateChanges, userInteractionEnabled: emptyStateItem == nil) let listState = ItemListNodeState(entries: entries, style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: crossfadeState, animateChanges: animateChanges, scrollEnabled: emptyStateItem == nil)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -222,27 +222,25 @@ public class ShareRootControllerImpl {
let requestUserInteraction: ([UnpreparedShareItemContent]) -> Signal<[PreparedShareItemContent], NoError> = { content in let requestUserInteraction: ([UnpreparedShareItemContent]) -> Signal<[PreparedShareItemContent], NoError> = { content in
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
switch content[0] { switch content[0] {
case let .contact(data): case let .contact(data):
let controller = deviceContactInfoController(context: context, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in let controller = deviceContactInfoController(context: context, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in
let phone = contactData.basicData.phoneNumbers[0].value let phone = contactData.basicData.phoneNumbers[0].value
if let vCardData = contactData.serializedVCard() { if let vCardData = contactData.serializedVCard() {
subscriber.putNext([.media(.media(.standalone(media: TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: vCardData))))]) subscriber.putNext([.media(.media(.standalone(media: TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: vCardData))))])
}
subscriber.putCompletion()
}), cancelled: {
cancelImpl?()
})
if let strongSelf = self, let window = strongSelf.mainWindow {
controller.presentationArguments = ViewControllerPresentationArguments(presentationAnimation: .modalSheet)
window.present(controller, on: .root)
} }
subscriber.putCompletion() break
}), cancelled: {
cancelImpl?()
})
if let strongSelf = self, let window = strongSelf.mainWindow {
controller.presentationArguments = ViewControllerPresentationArguments(presentationAnimation: .modalSheet)
window.present(controller, on: .root)
}
break
} }
return EmptyDisposable
return ActionDisposable { } |> runOn(Queue.mainQueue())
}
} |> runOn(Queue.mainQueue())
} }
let sentItems: ([PeerId], [PreparedShareItemContent], Account) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, contents, account in let sentItems: ([PeerId], [PreparedShareItemContent], Account) -> Signal<ShareControllerExternalStatus, NoError> = { peerIds, contents, account in
@ -271,17 +269,17 @@ public class ShareRootControllerImpl {
return .single(.done) return .single(.done)
} }
switch state { switch state {
case .preparing: case .preparing:
return .single(.preparing) return .single(.preparing)
case let .progress(value): case let .progress(value):
return .single(.progress(value)) return .single(.progress(value))
case let .userInteractionRequired(value): case let .userInteractionRequired(value):
return requestUserInteraction(value) return requestUserInteraction(value)
|> mapToSignal { contents -> Signal<ShareControllerExternalStatus, NoError> in |> mapToSignal { contents -> Signal<ShareControllerExternalStatus, NoError> in
return sentItems(peerIds, contents, account) return sentItems(peerIds, contents, account)
} }
case let .done(contents): case let .done(contents):
return sentItems(peerIds, contents, account) return sentItems(peerIds, contents, account)
} }
} }
} else { } else {
@ -338,7 +336,7 @@ public class ShareRootControllerImpl {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
})]) })])
strongSelf.mainWindow?.present(controller, on: .root) strongSelf.mainWindow?.present(controller, on: .root)
}, completed: {})) }, completed: {}))
} }
} }
} }

View File

@ -178,7 +178,12 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
} }
} }
} else { } else {
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 5 * 1024 * 1024) var thumbnailData: Data?
if mimeType == "application/pdf", let image = generatePdfPreviewImage(data: data, size: CGSize(width: 256.0, height: 256.0)), let jpegData = UIImageJPEGRepresentation(image, 0.5) {
thumbnailData = jpegData
}
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 5 * 1024 * 1024)
|> mapError { _ -> Void in return Void() } |> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in |> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event { switch event {

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import TelegramUIPreferences
enum StickerPackPreviewControllerMode { enum StickerPackPreviewControllerMode {
case `default` case `default`
@ -140,8 +141,13 @@ final class StickerPackPreviewController: ViewController {
} }
let account = self.context.account let account = self.context.account
self.displayNodeDidLoad() self.displayNodeDidLoad()
self.stickerPackDisposable.set((self.stickerPackContents.get() self.stickerPackDisposable.set((combineLatest(self.stickerPackContents.get(), self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> take(1))
|> mapToSignal { next -> Signal<LoadedStickerPack, NoError> in |> mapToSignal { next, sharedData -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value
}
switch next { switch next {
case let .result(_, items, _): case let .result(_, items, _):
var preloadSignals: [Signal<Bool, NoError>] = [] var preloadSignals: [Signal<Bool, NoError>] = []
@ -172,26 +178,26 @@ final class StickerPackPreviewController: ViewController {
return !values.contains(false) return !values.contains(false)
} }
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { loaded -> Signal<LoadedStickerPack, NoError> in |> mapToSignal { loaded -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
if !loaded { if !loaded {
return .single(.fetching) return .single((.fetching, stickerSettings))
} else { } else {
return .single(next) return .single((next, stickerSettings))
} }
} }
default: default:
return .single(next) return .single((next, stickerSettings))
} }
} }
|> deliverOnMainQueue).start(next: { [weak self] next in |> deliverOnMainQueue).start(next: { [weak self] next in
if let strongSelf = self { if let strongSelf = self {
if case .none = next { if case .none = next.0 {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.dismiss() strongSelf.dismiss()
} else { } else {
strongSelf.controllerNode.updateStickerPack(next) strongSelf.controllerNode.updateStickerPack(next.0, stickerSettings: next.1)
strongSelf.stickerPackContentsValue = next strongSelf.stickerPackContentsValue = next.0
} }
} }
})) }))

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences
private struct StickerPackPreviewGridEntry: Comparable, Identifiable { private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
let index: Int let index: Int
@ -75,6 +76,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
private var stickerPack: LoadedStickerPack? private var stickerPack: LoadedStickerPack?
private var stickerPackUpdated = false private var stickerPackUpdated = false
private var stickerPackInitiallyInstalled : Bool? private var stickerPackInitiallyInstalled : Bool?
private var stickerSettings: StickerSettings?
private var currentItems: [StickerPackPreviewGridEntry] = [] private var currentItems: [StickerPackPreviewGridEntry] = []
@ -131,13 +133,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
super.init() super.init()
self.interaction = StickerPackPreviewInteraction(sendSticker: { [weak self] item in self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false)
if let strongSelf = self, let sendSticker = strongSelf.sendSticker {
/*if sendSticker(item.file) {
strongSelf.cancel?()
}*/
}
})
self.backgroundColor = nil self.backgroundColor = nil
self.isOpaque = false self.isOpaque = false
@ -375,7 +371,8 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
if self.currentItems.isEmpty && !updatedItems.isEmpty { if self.currentItems.isEmpty && !updatedItems.isEmpty {
let entities = generateTextEntities(info.title, enabledTypes: [.mention]) let entities = generateTextEntities(info.title, enabledTypes: [.mention])
self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: Font.medium(20.0), linkFont: Font.medium(20.0), boldFont: Font.medium(20.0), italicFont: Font.medium(20.0), boldItalicFont: Font.medium(20.0), fixedFont: Font.medium(20.0)) let font = Font.medium(20.0)
self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font)
animateIn = true animateIn = true
} }
transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, account: self.context.account, interaction: self.interaction) transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, account: self.context.account, interaction: self.interaction)
@ -510,16 +507,16 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
} else { } else {
dismissOnAction = true dismissOnAction = true
} }
if let stickerPack = self.stickerPack { if let stickerPack = self.stickerPack, let stickerSettings = self.stickerSettings {
switch stickerPack { switch stickerPack {
case let .result(info, items, installed): case let .result(info, items, installed):
if installed { if installed {
let _ = removeStickerPackInteractively(postbox: self.context.account.postbox, id: info.id, option: .delete).start() let _ = removeStickerPackInteractively(postbox: self.context.account.postbox, id: info.id, option: .delete).start()
updateStickerPack(.result(info: info, items: items, installed: false)) self.updateStickerPack(.result(info: info, items: items, installed: false), stickerSettings: stickerSettings)
} else { } else {
let _ = addStickerPackInteractively(postbox: self.context.account.postbox, info: info, items: items).start() let _ = addStickerPackInteractively(postbox: self.context.account.postbox, info: info, items: items).start()
if !dismissOnAction { if !dismissOnAction {
updateStickerPack(.result(info: info, items: items, installed: true)) self.updateStickerPack(.result(info: info, items: items, installed: true), stickerSettings: stickerSettings)
} }
} }
if dismissOnAction { if dismissOnAction {
@ -566,9 +563,13 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
}) })
} }
func updateStickerPack(_ stickerPack: LoadedStickerPack) { func updateStickerPack(_ stickerPack: LoadedStickerPack, stickerSettings: StickerSettings) {
self.stickerPack = stickerPack self.stickerPack = stickerPack
self.stickerSettings = stickerSettings
self.stickerPackUpdated = true self.stickerPackUpdated = true
self.interaction.playAnimatedStickers = stickerSettings.loopAnimatedStickers
if let _ = self.containerLayout { if let _ = self.containerLayout {
self.dequeueUpdateStickerPack() self.dequeueUpdateStickerPack()
} }

View File

@ -8,11 +8,10 @@ import Postbox
final class StickerPackPreviewInteraction { final class StickerPackPreviewInteraction {
var previewedItem: StickerPreviewPeekItem? var previewedItem: StickerPreviewPeekItem?
var playAnimatedStickers: Bool
let sendSticker: (StickerPackItem) -> Void init(playAnimatedStickers: Bool) {
self.playAnimatedStickers = playAnimatedStickers
init(sendSticker: @escaping (StickerPackItem) -> Void) {
self.sendSticker = sendSticker
} }
} }
@ -53,12 +52,10 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
override var isVisibleInGrid: Bool { override var isVisibleInGrid: Bool {
didSet { didSet {
self.animationNode?.visibility = self.isVisibleInGrid self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
} }
} }
private let textNode: ASTextNode
private var currentIsPreviewing = false private var currentIsPreviewing = false
private let stickerFetchedDisposable = MetaDisposable() private let stickerFetchedDisposable = MetaDisposable()
@ -74,16 +71,10 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
override init() { override init() {
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = !smartInvertColorsEnabled() self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
//self.imageNode.alphaTransitionOnFirstUpdate = true
self.textNode = ASTextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = true
super.init() super.init()
self.addSubnode(self.imageNode) self.addSubnode(self.imageNode)
//self.addSubnode(self.textNode)
} }
deinit { deinit {
@ -100,14 +91,6 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
self.interaction = interaction self.interaction = interaction
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem {
var text = ""
for attribute in stickerItem.file.attributes {
if case let .Sticker(displayText, _, _) = attribute {
text = displayText
break
}
}
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .black, paragraphAlignment: .right)
if let dimensions = stickerItem.file.dimensions { if let dimensions = stickerItem.file.dimensions {
if stickerItem.file.isAnimatedSticker { if stickerItem.file.isAnimatedSticker {
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: CGSize(width: 160.0, height: 160.0))) self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: CGSize(width: 160.0, height: 160.0)))
@ -121,7 +104,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
} }
} }
self.animationNode?.setup(account: account, resource: stickerItem.file.resource, width: 160, height: 160, mode: .cached) self.animationNode?.setup(account: account, resource: stickerItem.file.resource, width: 160, height: 160, mode: .cached)
self.animationNode?.visibility = self.isVisibleInGrid self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start()) self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
} else { } else {
if let animationNode = self.animationNode { if let animationNode = self.animationNode {
@ -157,9 +140,6 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize) animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
animationNode.updateLayout(size: imageSize) animationNode.updateLayout(size: imageSize)
} }
let boundingFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - boundingSize.width) / 2.0), y: (bounds.size.height - boundingSize.height) / 2.0), size: boundingSize)
let textSize = CGSize(width: 32.0, height: 24.0)
self.textNode.frame = CGRect(origin: CGPoint(x: boundingFrame.maxX - 1.0 - textSize.width, y: boundingFrame.height + 10.0 - textSize.height), size: textSize)
} }
} }
@ -169,7 +149,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) { @objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
if let interaction = self.interaction, let (_, item, _) = self.currentState, case .ended = recognizer.state { if let interaction = self.interaction, let (_, item, _) = self.currentState, case .ended = recognizer.state {
interaction.sendSticker(item) //interaction.sendSticker(item)
} }
} }

View File

@ -45,24 +45,25 @@ func chatInputStateStringWithAppliedEntities(_ text: String, entities: [MessageT
return string return string
} }
func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, underlineLinks: Bool = true, external: Bool = false) -> NSAttributedString { func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false) -> NSAttributedString {
var nsString: NSString? var nsString: NSString?
let string = NSMutableAttributedString(string: text, attributes: [NSAttributedStringKey.font: baseFont, NSAttributedStringKey.foregroundColor: baseColor]) let string = NSMutableAttributedString(string: text, attributes: [NSAttributedStringKey.font: baseFont, NSAttributedStringKey.foregroundColor: baseColor])
var skipEntity = false var skipEntity = false
let stringLength = string.length
var underlineAllLinks = false var underlineAllLinks = false
if linkColor.isEqual(baseColor) { if linkColor.isEqual(baseColor) {
underlineAllLinks = true underlineAllLinks = true
} }
var fontAttributes: [NSRange: ChatTextFontAttributes] = [:] var fontAttributes: [NSRange: ChatTextFontAttributes] = [:]
var rangeOffset: Int = 0
for i in 0 ..< entities.count { for i in 0 ..< entities.count {
if skipEntity { if skipEntity {
skipEntity = false skipEntity = false
continue continue
} }
let stringLength = string.length
let entity = entities[i] let entity = entities[i]
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) var range = NSRange(location: entity.range.lowerBound + rangeOffset, length: entity.range.upperBound - entity.range.lowerBound)
if nsString == nil { if nsString == nil {
nsString = text as NSString nsString = text as NSString
} }
@ -188,6 +189,25 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.BotCommand), value: nsString!.substring(with: range), range: range) string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.BotCommand), value: nsString!.substring(with: range), range: range)
case .Code, .Pre: case .Code, .Pre:
string.addAttribute(NSAttributedStringKey.font, value: fixedFont, range: range) string.addAttribute(NSAttributedStringKey.font, value: fixedFont, range: range)
case .BlockQuote:
if let fontAttribute = fontAttributes[range] {
fontAttributes[range] = fontAttribute.union(.blockQuote)
} else {
fontAttributes[range] = .blockQuote
}
let paragraphBreak = "\n"
string.insert(NSAttributedString(string: paragraphBreak), at: range.lowerBound)
let paragraphRange = NSRange(location: range.lowerBound + paragraphBreak.count, length: range.upperBound - range.lowerBound)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.headIndent = 10.0
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: paragraphStyle.headIndent, options: [:])]
string.addAttribute(NSAttributedStringKey.paragraphStyle, value: paragraphStyle, range: paragraphRange)
string.insert(NSAttributedString(string: paragraphBreak), at: paragraphRange.upperBound)
rangeOffset += paragraphBreak.count
case let .Custom(type): case let .Custom(type):
if type == ApplicationSpecificEntityType.Timecode { if type == ApplicationSpecificEntityType.Timecode {
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
@ -208,7 +228,9 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
for (range, fontAttributes) in fontAttributes { for (range, fontAttributes) in fontAttributes {
var font: UIFont? var font: UIFont?
if fontAttributes == [.bold, .italic] { if fontAttributes.contains(.blockQuote) {
font = blockQuoteFont
} else if fontAttributes == [.bold, .italic] {
font = boldItalicFont font = boldItalicFont
} else if fontAttributes == [.bold] { } else if fontAttributes == [.bold] {
font = boldFont font = boldFont

View File

@ -553,7 +553,7 @@ static void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t numBits, int32_t
// add output reader to reader // add output reader to reader
[iPodAssetReader addOutput: readerOutput]; [iPodAssetReader addOutput: readerOutput];
if (! [iPodAssetReader startReading]) { if (![iPodAssetReader startReading]) {
NSLog(@"Unable to start reading!"); NSLog(@"Unable to start reading!");
return nil; return nil;
} }
@ -563,7 +563,6 @@ static void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t numBits, int32_t
int _waveformPeakCount = 0; int _waveformPeakCount = 0;
while (iPodAssetReader.status == AVAssetReaderStatusReading) { while (iPodAssetReader.status == AVAssetReaderStatusReading) {
// Check if the available buffer space is enough to hold at least one cycle of the sample data
CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer]; CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer];
if (nextBuffer) { if (nextBuffer) {

View File

@ -57,7 +57,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode {
self.contentTextNode = ImmediateTextNode() self.contentTextNode = ImmediateTextNode()
self.contentTextNode.displaysAsynchronously = false self.contentTextNode.displaysAsynchronously = false
self.contentTextNode.maximumNumberOfLines = 0 self.contentTextNode.maximumNumberOfLines = 0
self.contentTextNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: theme.primary, linkColor: theme.accent, baseFont: Font.regular(15.0), linkFont: Font.regular(15.0), boldFont: Font.semibold(15.0), italicFont: Font.italic(15.0), boldItalicFont: Font.semiboldItalic(15.0), fixedFont: Font.monospace(15.0)) self.contentTextNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: theme.primary, linkColor: theme.accent, baseFont: Font.regular(15.0), linkFont: Font.regular(15.0), boldFont: Font.semibold(15.0), italicFont: Font.italic(15.0), boldItalicFont: Font.semiboldItalic(15.0), fixedFont: Font.monospace(15.0), blockQuoteFont: Font.regular(15.0))
self.toolbarNode = ASDisplayNode() self.toolbarNode = ASDisplayNode()
self.toolbarSeparatorNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode()

View File

@ -41,4 +41,5 @@ struct TelegramTextAttributes {
static let BotCommand = "TelegramBotCommand" static let BotCommand = "TelegramBotCommand"
static let Hashtag = "TelegramHashtag" static let Hashtag = "TelegramHashtag"
static let Timecode = "TelegramTimecode" static let Timecode = "TelegramTimecode"
static let BlockQuote = "TelegramBlockQuote"
} }

View File

@ -113,9 +113,9 @@ public func updateInfoController(context: AccountContext, appUpdateInfo: AppUpda
appIcon = appIcons.filter { $0.isDefault }.first appIcon = appIcons.filter { $0.isDefault }.first
} }
let leftNavigationButton = appUpdateInfo.popup ? ItemListNavigationButton(content: .text(presentationData.strings.Update_Skip), style: .regular, enabled: true, action: { let leftNavigationButton = appUpdateInfo.blocking ? nil : ItemListNavigationButton(content: .text(presentationData.strings.Update_Skip), style: .regular, enabled: true, action: {
dismissImpl?() dismissImpl?()
}) : nil })
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Update_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Update_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: updateInfoControllerEntries(theme: presentationData.theme, strings: presentationData.strings, appIcon: appIcon, appUpdateInfo: appUpdateInfo), style: .blocks, animateChanges: false) let listState = ItemListNodeState(entries: updateInfoControllerEntries(theme: presentationData.theme, strings: presentationData.strings, appIcon: appIcon, appUpdateInfo: appUpdateInfo), style: .blocks, animateChanges: false)

View File

@ -208,7 +208,7 @@ class UpdateInfoItemNode: ListViewItemNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 88.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 88.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let string = stringWithAppliedEntities(item.text, entities: item.entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) let string = stringWithAppliedEntities(item.text, entities: item.entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textFont)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize: CGSize let contentSize: CGSize

View File

@ -270,7 +270,7 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
} }
} else { } else {
if let peer = peer as? TelegramUser, peer.botInfo == nil { if let peer = peer as? TelegramUser, peer.botInfo == nil {
return .peer(peer.id, .info) return .peer(peer.id, .chat(textInputState: nil, messageId: nil))
} else { } else {
return .peer(peer.id, .chat(textInputState: nil, messageId: nil)) return .peer(peer.id, .chat(textInputState: nil, messageId: nil))
} }

View File

@ -151,14 +151,14 @@ public struct MediaAutoDownloadSettings: PreferencesEntry, Equatable {
public static var defaultSettings: MediaAutoDownloadSettings { public static var defaultSettings: MediaAutoDownloadSettings {
let mb: Int32 = 1024 * 1024 let mb: Int32 = 1024 * 1024
let presets = MediaAutoDownloadPresets(low: MediaAutoDownloadCategories(basePreset: .low, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), let presets = MediaAutoDownloadPresets(low: MediaAutoDownloadCategories(basePreset: .low, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false),
video: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false), video: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false),
file: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false)), file: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false)),
medium: MediaAutoDownloadCategories(basePreset: .medium, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), medium: MediaAutoDownloadCategories(basePreset: .medium, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false),
video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: Int32(2.5 * CGFloat(mb)), predownload: false), video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: Int32(2.5 * CGFloat(mb)), predownload: false),
file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false)), file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false)),
high: MediaAutoDownloadCategories(basePreset: .high, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), high: MediaAutoDownloadCategories(basePreset: .high, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false),
video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 10 * mb, predownload: true), video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 10 * mb, predownload: true),
file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 3 * mb, predownload: false))) file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 3 * mb, predownload: false)))
let saveDownloadedPhotos = MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 0, predownload: false) let saveDownloadedPhotos = MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 0, predownload: false)
return MediaAutoDownloadSettings(presets: presets, cellular: MediaAutoDownloadConnection(enabled: true, preset: .medium, custom: nil), wifi: MediaAutoDownloadConnection(enabled: true, preset: .high, custom: nil), saveDownloadedPhotos: saveDownloadedPhotos, autoplayGifs: true, autoplayVideos: true, downloadInBackground: true) return MediaAutoDownloadSettings(presets: presets, cellular: MediaAutoDownloadConnection(enabled: true, preset: .medium, custom: nil), wifi: MediaAutoDownloadConnection(enabled: true, preset: .high, custom: nil), saveDownloadedPhotos: saveDownloadedPhotos, autoplayGifs: true, autoplayVideos: true, downloadInBackground: true)

View File

@ -10,21 +10,25 @@ public enum EmojiStickerSuggestionMode: Int32 {
public struct StickerSettings: PreferencesEntry, Equatable { public struct StickerSettings: PreferencesEntry, Equatable {
public var emojiStickerSuggestionMode: EmojiStickerSuggestionMode public var emojiStickerSuggestionMode: EmojiStickerSuggestionMode
public var loopAnimatedStickers: Bool
public static var defaultSettings: StickerSettings { public static var defaultSettings: StickerSettings {
return StickerSettings(emojiStickerSuggestionMode: .all) return StickerSettings(emojiStickerSuggestionMode: .all, loopAnimatedStickers: true)
} }
init(emojiStickerSuggestionMode: EmojiStickerSuggestionMode) { init(emojiStickerSuggestionMode: EmojiStickerSuggestionMode, loopAnimatedStickers: Bool) {
self.emojiStickerSuggestionMode = emojiStickerSuggestionMode self.emojiStickerSuggestionMode = emojiStickerSuggestionMode
self.loopAnimatedStickers = loopAnimatedStickers
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.emojiStickerSuggestionMode = EmojiStickerSuggestionMode(rawValue: decoder.decodeInt32ForKey("emojiStickerSuggestionMode", orElse: 0))! self.emojiStickerSuggestionMode = EmojiStickerSuggestionMode(rawValue: decoder.decodeInt32ForKey("emojiStickerSuggestionMode", orElse: 0))!
self.loopAnimatedStickers = decoder.decodeBoolForKey("loopAnimatedStickers", orElse: true)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.emojiStickerSuggestionMode.rawValue, forKey: "emojiStickerSuggestionMode") encoder.encodeInt32(self.emojiStickerSuggestionMode.rawValue, forKey: "emojiStickerSuggestionMode")
encoder.encodeBool(self.loopAnimatedStickers, forKey: "loopAnimatedStickers")
} }
public func isEqual(to: PreferencesEntry) -> Bool { public func isEqual(to: PreferencesEntry) -> Bool {
@ -36,11 +40,15 @@ public struct StickerSettings: PreferencesEntry, Equatable {
} }
public static func ==(lhs: StickerSettings, rhs: StickerSettings) -> Bool { public static func ==(lhs: StickerSettings, rhs: StickerSettings) -> Bool {
return lhs.emojiStickerSuggestionMode == rhs.emojiStickerSuggestionMode return lhs.emojiStickerSuggestionMode == rhs.emojiStickerSuggestionMode && lhs.loopAnimatedStickers == rhs.loopAnimatedStickers
} }
public func withUpdatedEmojiStickerSuggestionMode(_ emojiStickerSuggestionMode: EmojiStickerSuggestionMode) -> StickerSettings { public func withUpdatedEmojiStickerSuggestionMode(_ emojiStickerSuggestionMode: EmojiStickerSuggestionMode) -> StickerSettings {
return StickerSettings(emojiStickerSuggestionMode: emojiStickerSuggestionMode) return StickerSettings(emojiStickerSuggestionMode: emojiStickerSuggestionMode, loopAnimatedStickers: self.loopAnimatedStickers)
}
public func withUpdatedLoopAnimatedStickers(_ loopAnimatedStickers: Bool) -> StickerSettings {
return StickerSettings(emojiStickerSuggestionMode: self.emojiStickerSuggestionMode, loopAnimatedStickers: loopAnimatedStickers)
} }
} }

View File

@ -2388,10 +2388,10 @@ simpleAudioBlock random_id:long random_bytes:string raw_data:string = DecryptedA
stm->jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_20", 6)); stm->jitterBuffer->SetMinPacketCount((uint32_t) ServerConfig::GetSharedInstance()->GetInt("jitter_initial_delay_20", 6));
stm->decoder=NULL; stm->decoder=NULL;
}else if(stm->type==STREAM_TYPE_VIDEO){ }else if(stm->type==STREAM_TYPE_VIDEO){
if(!stm->packetReassembler){ // if(!stm->packetReassembler){
stm->packetReassembler=make_shared<PacketReassembler>(); // stm->packetReassembler=make_shared<PacketReassembler>();
stm->packetReassembler->SetCallback(bind(&VoIPController::ProcessIncomingVideoFrame, this, placeholders::_1, placeholders::_2, placeholders::_3)); // stm->packetReassembler->SetCallback(bind(&VoIPController::ProcessIncomingVideoFrame, this, placeholders::_1, placeholders::_2, placeholders::_3));
} // }
}else{ }else{
LOGW("Unknown incoming stream type: %d", stm->type); LOGW("Unknown incoming stream type: %d", stm->type);
continue; continue;