Location view fixes

This commit is contained in:
Ilya Laktyushin 2020-10-26 12:47:25 +04:00
parent c8734c7728
commit 94ece899d8
14 changed files with 249 additions and 125 deletions

View File

@ -105,7 +105,7 @@ final class LocationActionListItem: ListViewItem {
async {
let node = LocationActionListItemNode()
let makeLayout = node.asyncLayout()
let (nodeLayout, nodeApply) = makeLayout(self, params, nextItem is LocationActionListItem)
let (nodeLayout, nodeApply) = makeLayout(self, params, nextItem is LocationActionListItem || nextItem is LocationLiveListItem)
node.contentSize = nodeLayout.contentSize
node.insets = nodeLayout.insets
@ -118,7 +118,7 @@ final class LocationActionListItem: ListViewItem {
if let nodeValue = node() as? LocationActionListItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params, nextItem is LocationActionListItem)
let (nodeLayout, apply) = layout(self, params, nextItem is LocationActionListItem || nextItem is LocationLiveListItem)
Queue.mainQueue().async {
completion(nodeLayout, { info in
apply().1(info)

View File

@ -96,7 +96,9 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
}
var id: String {
if let peer = self.peer {
if let message = self.message {
return "\(message.id.id)"
} else if let peer = self.peer {
return "\(peer.id.toInt64())"
} else if let venueId = self.location?.venue?.id {
return venueId
@ -257,8 +259,8 @@ class LocationPinAnnotationView: MKAnnotationView {
self.dotNode.isHidden = false
self.backgroundNode.image = UIImage(bundleImageName: "Location/PinBackground")
if let author = message.author, let peer = message.peers[author.id] {
self.setPeer(context: annotation.context, theme: annotation.theme, peer: peer)
if let author = message.author {
self.setPeer(context: annotation.context, theme: annotation.theme, peer: author)
} else if let selfPeer = annotation.selfPeer {
self.setPeer(context: annotation.context, theme: annotation.theme, peer: selfPeer)
}

View File

@ -338,8 +338,14 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
private func updateDoneButtonTitle() {
if let pickerView = self.pickerView {
let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)]
let selectedLargeRow = pickerView.selectedRow(inComponent: 0)
var selectedSmallRow = pickerView.selectedRow(inComponent: 1)
if selectedLargeRow == 0 && selectedSmallRow == 0 {
selectedSmallRow = 1
}
let largeValue = unitValues[selectedLargeRow]
let smallValue = smallUnitValues[selectedSmallRow]
var value = largeValue * 1000 + smallValue * 10
if !self.usesMetricSystem() {

View File

@ -6,7 +6,10 @@ import Display
import SwiftSignalKit
import TelegramCore
import SyncCore
import AccountContext
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI
import LocationResources
import AppBundle
@ -15,15 +18,19 @@ import LiveLocationTimerNode
final class LocationLiveListItem: ListViewItem {
let presentationData: ItemListPresentationData
let account: Account
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
let context: AccountContext
let message: Message
let distance: Double?
let action: () -> Void
let longTapAction: () -> Void
public init(presentationData: ItemListPresentationData, account: Account, message: Message, distance: Double?, action: @escaping () -> Void, longTapAction: @escaping () -> Void = { }) {
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, message: Message, distance: Double?, action: @escaping () -> Void, longTapAction: @escaping () -> Void = { }) {
self.presentationData = presentationData
self.account = account
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.context = context
self.message = message
self.distance = distance
self.action = action
@ -92,6 +99,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
self.highlightedBackgroundNode.isLayerBacked = true
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
@ -146,20 +154,39 @@ final class LocationLiveListItemNode: ListViewItemNode {
let leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = params.rightInset
let verticalInset: CGFloat = 8.0
let iconSize: CGFloat = 40.0
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let titleAttributedString = NSAttributedString(string: "title", font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var title: String = ""
if let author = item.message.author {
title = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.nameDisplayOrder)
}
let titleAttributedString = NSAttributedString(string: title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 54.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let subtitleAttributedString = NSAttributedString(string: "subtitle", font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var updateTimestamp = item.message.timestamp
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
updateTimestamp = attribute.date
break
}
}
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let timeString = stringForRelativeLiveLocationTimestamp(strings: item.presentationData.strings, relativeTimestamp: Int32(updateTimestamp), relativeTo: Int32(timestamp), dateTimeFormat: item.dateTimeFormat)
var subtitle = timeString
if let distance = item.distance {
let distanceString = item.presentationData.strings.Map_DistanceAway(stringForDistance(strings: item.presentationData.strings, distance: distance)).0
subtitle = "\(timeString)\(distanceString)"
}
let subtitleAttributedString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 54.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let titleSpacing: CGFloat = 1.0
let bottomInset: CGFloat = hasSeparator ? 0.0 : 4.0
let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset)
let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height)
let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
return (nodeLayout, { [weak self] in
@ -168,7 +195,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
updatedTheme = item.presentationData.theme
}
return (nil, { _ in
return (self?.avatarNode.ready, { _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
@ -199,32 +226,41 @@ final class LocationLiveListItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel
let topHighlightInset: CGFloat = separatorHeight
let avatarSize: CGFloat = 40.0
// let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize))
// strongSelf.iconNode.frame = iconNodeFrame
// strongSelf.venueIconNode.frame = iconNodeFrame
if let peer = item.message.author {
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: nil, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: false)
}
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
strongSelf.separatorNode.isHidden = !hasSeparator
// if let (beginTimestamp, timeout) = item.beginTimeAndTimeout {
// let timerNode: ChatMessageLiveLocationTimerNode
// if let current = strongSelf.timerNode {
// timerNode = current
// } else {
// timerNode = ChatMessageLiveLocationTimerNode()
// strongSelf.addSubnode(timerNode)
// strongSelf.timerNode = timerNode
// }
// let timerSize = CGSize(width: 28.0, height: 28.0)
// timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: timeout, strings: item.presentationData.strings)
// timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize)
// } else if let timerNode = strongSelf.timerNode {
// strongSelf.timerNode = nil
// timerNode.removeFromSupernode()
// }
var liveBroadcastingTimeout: Int32 = 0
if let location = getLocation(from: item.message), let timeout = location.liveBroadcastingTimeout {
liveBroadcastingTimeout = timeout
}
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if currentTimestamp < item.message.timestamp + liveBroadcastingTimeout {
let timerNode: ChatMessageLiveLocationTimerNode
if let current = strongSelf.timerNode {
timerNode = current
} else {
timerNode = ChatMessageLiveLocationTimerNode()
strongSelf.addSubnode(timerNode)
strongSelf.timerNode = timerNode
}
let timerSize = CGSize(width: 28.0, height: 28.0)
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: Double(item.message.timestamp), timeout: Double(liveBroadcastingTimeout), strings: item.presentationData.strings)
timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0)), size: timerSize)
} else if let timerNode = strongSelf.timerNode {
strongSelf.timerNode = nil
timerNode.removeFromSupernode()
}
}
})
})

View File

@ -758,7 +758,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
if let zoomRect = zoomRect {
let insets = UIEdgeInsets(top: 0.0, left: 80.0, bottom: 0.0, right: 80.0)
let insets = UIEdgeInsets(top: 88.0, left: 80.0, bottom: 160.0, right: 80.0)
let fittedZoomRect = mapView.mapRectThatFits(zoomRect, edgePadding: insets)
mapView.setVisibleMapRect(fittedZoomRect, animated: animated)
}

View File

@ -11,6 +11,7 @@ import ItemListUI
import ItemListVenueItem
import TelegramPresentationData
import TelegramStringFormatting
import TelegramUIPreferences
import TelegramNotices
import AccountContext
import AppBundle
@ -48,7 +49,7 @@ private enum LocationViewEntryId: Hashable {
private enum LocationViewEntry: Comparable, Identifiable {
case info(PresentationTheme, TelegramMediaMap, String?, Double?, Double?)
case toggleLiveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?, Double?, Double?)
case liveLocation(PresentationTheme, Message, Double?, Int)
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, Message, Double?, Int)
var stableId: LocationViewEntryId {
switch self {
@ -56,7 +57,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
return .info
case .toggleLiveLocation:
return .toggleLiveLocation
case let .liveLocation(_, message, _, _):
case let .liveLocation(_, _, _, message, _, _):
return .liveLocation(message.stableId)
}
}
@ -75,8 +76,8 @@ private enum LocationViewEntry: Comparable, Identifiable {
} else {
return false
}
case let .liveLocation(lhsTheme, lhsMessage, lhsDistance, lhsIndex):
if case let .liveLocation(rhsTheme, rhsMessage, rhsDistance, rhsIndex) = rhs, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsIndex == rhsIndex {
case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsIndex):
if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsIndex == rhsIndex {
return true
} else {
return false
@ -100,17 +101,17 @@ private enum LocationViewEntry: Comparable, Identifiable {
case .liveLocation:
return true
}
case let .liveLocation(_, _, _, lhsIndex):
case let .liveLocation(_, _, _, _, _, lhsIndex):
switch rhs {
case .info, .toggleLiveLocation:
return false
case let .liveLocation(_, _, _, rhsIndex):
case let .liveLocation(_, _, _, _, _, rhsIndex):
return lhsIndex < rhsIndex
}
}
}
func item(account: Account, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
func item(context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
switch self {
case let .info(_, location, address, distance, time):
let addressString: String?
@ -126,7 +127,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
distanceString = nil
}
let eta = time.flatMap { stringForEstimatedDuration(strings: presentationData.strings, eta: $0) }
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), account: account, location: location, address: addressString, distance: distanceString, eta: eta, action: {
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), account: context.account, location: location, address: addressString, distance: distanceString, eta: eta, action: {
interaction?.goToCoordinate(location.coordinate)
}, getDirections: {
interaction?.requestDirections()
@ -138,7 +139,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
} else {
beginTimeAndTimeout = nil
}
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: context.account, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
if beginTimeAndTimeout != nil {
interaction?.stopLiveLocation()
} else if let coordinate = coordinate {
@ -147,24 +148,18 @@ private enum LocationViewEntry: Comparable, Identifiable {
}, highlighted: { highlight in
interaction?.updateSendActionHighlight(highlight)
})
case let .liveLocation(_, message, distance, _):
let distanceString: String?
if let distance = distance {
distanceString = distance < 10 ? presentationData.strings.Map_YouAreHere : presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: distance)).0
} else {
distanceString = nil
}
return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), account: account, message: message, distance: distance, action: {}, longTapAction: {})
case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, _):
return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, action: {}, longTapAction: {})
}
}
}
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], account: Account, presentationData: PresentationData, interaction: LocationViewInteraction?) -> LocationViewTransaction {
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> LocationViewTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates)
}
@ -401,12 +396,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
// entries.append(.liveLocation(presentationData.theme, message, distance, index))
if message.localTags.contains(.OutgoingLiveLocation), let selfPeer = selfPeer {
userAnnotation = LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, heading: location.heading)
} else {
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, heading: location.heading))
entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, index))
}
index += 1
}
@ -414,11 +409,14 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
if subject.id.peerId.namespace != Namespaces.Peer.CloudUser, proximityNotification == nil {
proximityNotification = false
}
if let channel = subject.author as? TelegramChannel, case .broadcast = channel.info {
proximityNotification = nil
}
let previousEntries = previousEntries.swap(entries)
let previousState = previousState.swap(state)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction)
strongSelf.enqueueTransition(transition)
strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false)

View File

@ -2465,8 +2465,8 @@ public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Si
private let precomposedSmallAlbumArt = Atomic<UIImage?>(value: nil)
private func albumArtThumbnailData(postbox: Postbox, thumbnail: MediaResource) -> Signal<Data?, NoError> {
let thumbnailResource = postbox.mediaBox.resourceData(thumbnail)
private func albumArtThumbnailData(postbox: Postbox, thumbnail: MediaResource, attemptSynchronously: Bool = false) -> Signal<Data?, NoError> {
let thumbnailResource = postbox.mediaBox.resourceData(thumbnail, attemptSynchronously: attemptSynchronously)
let signal = thumbnailResource |> take(1) |> mapToSignal { maybeData -> Signal<Data?, NoError> in
if maybeData.complete {
@ -2602,7 +2602,7 @@ private func drawAlbumArtPlaceholder(into c: CGContext, arguments: TransformImag
}
}
public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool, overlayColor: UIColor? = nil, emptyColor: UIColor? = nil, drawPlaceholderWhenEmpty: Bool = true) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool, overlayColor: UIColor? = nil, emptyColor: UIColor? = nil, drawPlaceholderWhenEmpty: Bool = true, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
var fileArtworkData: Signal<Data?, NoError> = .single(nil)
if let fileReference = fileReference {
let size = thumbnail ? CGSize(width: 48.0, height: 48.0) : CGSize(width: 320.0, height: 320.0)
@ -2628,7 +2628,7 @@ public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?,
let thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource).start(next: { next in
let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: attemptSynchronously).start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, error: subscriber.putError, completed: subscriber.putCompletion)
@ -2643,7 +2643,7 @@ public func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?,
}
} else if let albumArt = albumArt {
if thumbnail {
immediateArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource)
immediateArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource, attemptSynchronously: attemptSynchronously)
|> map { thumbnailData in
return Tuple(thumbnailData, nil, false)
}

View File

@ -591,8 +591,8 @@ private final class SemanticStatusNodeAppearanceContext {
self.cutout = cutout
}
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeAppearanceDrawingState {
return SemanticStatusNodeAppearanceDrawingState(transitionFraction: transitionFraction, background: self.background, foreground: self.foreground, backgroundImage: self.backgroundImage, overlayForeground: self.overlayForeground, cutout: self.cutout)
func drawingState(backgroundTransitionFraction: CGFloat, foregroundTransitionFraction: CGFloat) -> SemanticStatusNodeAppearanceDrawingState {
return SemanticStatusNodeAppearanceDrawingState(backgroundTransitionFraction: backgroundTransitionFraction, foregroundTransitionFraction: foregroundTransitionFraction, background: self.background, foreground: self.foreground, backgroundImage: self.backgroundImage, overlayForeground: self.overlayForeground, cutout: self.cutout)
}
func withUpdatedBackground(_ background: UIColor) -> SemanticStatusNodeAppearanceContext {
@ -617,7 +617,8 @@ private final class SemanticStatusNodeAppearanceContext {
}
private final class SemanticStatusNodeAppearanceDrawingState {
let transitionFraction: CGFloat
let backgroundTransitionFraction: CGFloat
let foregroundTransitionFraction: CGFloat
let background: UIColor
let foreground: UIColor
let backgroundImage: UIImage?
@ -632,8 +633,9 @@ private final class SemanticStatusNodeAppearanceDrawingState {
}
}
init(transitionFraction: CGFloat, background: UIColor, foreground: UIColor, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: CGRect?) {
self.transitionFraction = transitionFraction
init(backgroundTransitionFraction: CGFloat, foregroundTransitionFraction: CGFloat, background: UIColor, foreground: UIColor, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: CGRect?) {
self.backgroundTransitionFraction = backgroundTransitionFraction
self.foregroundTransitionFraction = foregroundTransitionFraction
self.background = background
self.foreground = foreground
self.backgroundImage = backgroundImage
@ -644,11 +646,12 @@ private final class SemanticStatusNodeAppearanceDrawingState {
func drawBackground(context: CGContext, size: CGSize) {
let bounds = CGRect(origin: CGPoint(), size: size)
context.setBlendMode(.normal)
if let backgroundImage = self.backgroundImage?.cgImage {
context.saveGState()
context.translateBy(x: 0.0, y: bounds.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setAlpha(self.transitionFraction)
context.setAlpha(self.backgroundTransitionFraction)
context.draw(backgroundImage, in: bounds)
context.restoreGState()
} else {
@ -659,7 +662,7 @@ private final class SemanticStatusNodeAppearanceDrawingState {
func drawForeground(context: CGContext, size: CGSize) {
if let cutout = self.cutout {
let size = CGSize(width: cutout.width * self.transitionFraction, height: cutout.height * self.transitionFraction)
let size = CGSize(width: cutout.width * self.foregroundTransitionFraction, height: cutout.height * self.foregroundTransitionFraction)
let rect = CGRect(origin: CGPoint(x: cutout.midX - size.width / 2.0, y: cutout.midY - size.height / 2.0), size: size)
context.setBlendMode(.clear)
@ -740,13 +743,23 @@ public final class SemanticStatusNode: ASControlNode {
return self.appearanceContext.cutout
}
set {
if self.appearanceContext.cutout != newValue {
self.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: self.appearanceContext, completion: {})
self.appearanceContext = self.appearanceContext.withUpdatedCutout(newValue)
self.setCutout(newValue, animated: false)
}
}
public func setCutout(_ cutout: CGRect?, animated: Bool) {
guard cutout != self.appearanceContext.cutout else {
return
}
if animated {
self.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.2, previousStateContext: nil, previousAppearanceContext: self.appearanceContext, completion: {})
self.appearanceContext = self.appearanceContext.withUpdatedCutout(cutout)
self.updateAnimations()
self.setNeedsDisplay()
}
} else {
self.appearanceContext = self.appearanceContext.withUpdatedCutout(cutout)
self.setNeedsDisplay()
}
}
@ -783,7 +796,7 @@ public final class SemanticStatusNode: ASControlNode {
let previousAppearanceContext = strongSelf.appearanceContext
strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(context?.generateImage())
if CACurrentMediaTime() - start > 0.2 {
if CACurrentMediaTime() - start > 0.3 {
strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {})
strongSelf.updateAnimations()
}
@ -858,7 +871,8 @@ public final class SemanticStatusNode: ASControlNode {
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
var transitionState: SemanticStatusNodeTransitionDrawingState?
var transitionFraction: CGFloat = 1.0
var appearanceTransitionFraction: CGFloat = 1.0
var appearanceBackgroundTransitionFraction: CGFloat = 1.0
var appearanceForegroundTransitionFraction: CGFloat = 1.0
if let transitionContext = self.transitionContext {
let timestamp = CACurrentMediaTime()
@ -868,13 +882,20 @@ public final class SemanticStatusNode: ASControlNode {
if let _ = transitionContext.previousStateContext {
transitionFraction = t
}
if let _ = transitionContext.previousAppearanceContext {
appearanceTransitionFraction = t
var foregroundTransitionFraction: CGFloat = 1.0
if let previousContext = transitionContext.previousAppearanceContext {
if previousContext.backgroundImage != self.appearanceContext.backgroundImage {
appearanceBackgroundTransitionFraction = t
}
transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext?.drawingState(transitionFraction: 1.0 - t), appearanceState: transitionContext.previousAppearanceContext?.drawingState(transitionFraction: 1.0 - t))
if previousContext.cutout != self.appearanceContext.cutout {
appearanceForegroundTransitionFraction = t
foregroundTransitionFraction = 1.0 - t
}
}
transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext?.drawingState(transitionFraction: 1.0 - t), appearanceState: transitionContext.previousAppearanceContext?.drawingState(backgroundTransitionFraction: 1.0, foregroundTransitionFraction: foregroundTransitionFraction))
}
return SemanticStatusNodeDrawingState(transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), appearanceState: self.appearanceContext.drawingState(transitionFraction: appearanceTransitionFraction))
return SemanticStatusNodeDrawingState(transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), appearanceState: self.appearanceContext.drawingState(backgroundTransitionFraction: appearanceBackgroundTransitionFraction, foregroundTransitionFraction: appearanceForegroundTransitionFraction))
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {

View File

@ -442,12 +442,12 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .phoneNumberRequest:
attributedString = nil
case let .geoProximityReached(_, toId, distance):
case let .geoProximityReached(fromId, toId, distance):
let distanceString = stringForDistance(strings: strings, distance: Double(distance))
if toId == accountPeerId {
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReachedYou(authorName, distanceString), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReachedYou(message.peers[fromId]?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "", distanceString), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
} else {
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReached(authorName, distanceString, message.peers[toId]?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id), (2, toId)]))
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReached(message.peers[fromId]?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "", distanceString, message.peers[toId]?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id), (2, toId)]))
}
case .unknown:
attributedString = nil

View File

@ -7858,6 +7858,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let selfPeerId: PeerId
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
selfPeerId = peer.id
} else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) {
selfPeerId = peer.id
} else {
selfPeerId = self.context.account.peerId
}
@ -8306,6 +8308,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
controller.dismissWithCommitAction()
}
})
self.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
return true
})
let value: String?
let emoji = dice.emoji.strippedEmoji

View File

@ -853,7 +853,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
views = attribute.count
}
}
if views >= 100 {
if let cachedData = cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats), views >= 100 {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextViewStats, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor)
}, action: { c, _ in

View File

@ -445,7 +445,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
func updateVisibility() {
private func updateVisibility() {
guard let item = self.item else {
return
}
@ -1104,7 +1104,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
@ -1268,7 +1268,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
return nil
}
@objc func shareButtonPressed() {
@objc private func shareButtonPressed() {
if let item = self.item {
if case .pinnedMessages = item.associatedData.subject {
item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id)
@ -1299,7 +1299,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
@objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
switch recognizer.state {
case .began:
self.currentSwipeToReplyTranslation = 0.0

View File

@ -829,11 +829,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
wasCheck = true
}
if isAudio && !isVoice {
state = .play
} else {
if adjustedProgress.isEqual(to: 1.0), (message.flags.contains(.Unsent) || wasCheck) {
state = .check(appearance: nil)
} else {
state = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: nil)
}
}
case .Local:
if isAudio {
state = .play
@ -897,7 +901,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
image = playerAlbumArt(postbox: context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: .init(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false)), thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), drawPlaceholderWhenEmpty: false)
image = playerAlbumArt(postbox: context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: .init(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false)), thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), drawPlaceholderWhenEmpty: false, attemptSynchronously: !animated)
}
let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor)
self.statusNode = statusNode
@ -975,11 +979,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let cutoutFrame = streamingCacheStatusFrame.insetBy(dx: -(1.0 + UIScreenPixel), dy: -(1.0 + UIScreenPixel)).offsetBy(dx: progressFrame.minX - 6.0, dy: progressFrame.minY)
if streamingState == .none && self.selectionNode == nil {
self.statusNode?.cutout = nil
self.statusNode?.setCutout(nil, animated: animated)
} else if let statusNode = self.statusNode, (self.iconNode?.isHidden ?? true) {
self.statusNode?.cutout = cutoutFrame
statusNode.setCutout(cutoutFrame, animated: true)
}
if let (expandedString, compactString, font) = downloadingStrings {

View File

@ -20,6 +20,42 @@ public enum PrefetchMediaItem {
case animatedEmojiSticker(TelegramMediaFile)
}
private struct AnimatedEmojiSoundsConfiguration {
static var defaultValue: AnimatedEmojiSoundsConfiguration {
return AnimatedEmojiSoundsConfiguration(sounds: [:])
}
public let sounds: [String: TelegramMediaFile]
fileprivate init(sounds: [String: TelegramMediaFile]) {
self.sounds = sounds
}
static func with(appConfiguration: AppConfiguration) -> AnimatedEmojiSoundsConfiguration {
if let data = appConfiguration.data, let values = data["emojies_sounds"] as? [String: Any] {
var sounds: [String: TelegramMediaFile] = [:]
for (key, value) in values {
if let dict = value as? [String: String], var fileReferenceString = dict["file_reference_base64"] {
fileReferenceString = fileReferenceString.replacingOccurrences(of: "-", with: "+")
fileReferenceString = fileReferenceString.replacingOccurrences(of: "_", with: "/")
while fileReferenceString.count % 4 != 0 {
fileReferenceString.append("=")
}
if let idString = dict["id"], let id = Int64(idString), let accessHashString = dict["access_hash"], let accessHash = Int64(accessHashString), let fileReference = Data(base64Encoded: fileReferenceString) {
let resource = CloudDocumentMediaResource(datacenterId: 0, fileId: id, accessHash: accessHash, size: nil, fileReference: fileReference, fileName: nil)
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: nil, attributes: [])
sounds[key] = file
}
}
}
return AnimatedEmojiSoundsConfiguration(sounds: sounds)
} else {
return .defaultValue
}
}
}
private final class PrefetchManagerImpl {
private let queue: Queue
private let account: Account
@ -45,12 +81,21 @@ private final class PrefetchManagerImpl {
}
|> distinctUntilChanged
let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia
|> mapToSignal { orderedPreloadMedia in
return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false)
|> map { result -> [PrefetchMediaItem] in
let appConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> take(1)
|> map { view in
return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
}
let orderedPreloadMedia = combineLatest(account.viewTracker.orderedPreloadMedia, loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false), appConfiguration)
|> map { orderedPreloadMedia, stickerPack, appConfiguration -> [PrefetchMediaItem] in
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration)
let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) }
switch result {
var stickerItems: [PrefetchMediaItem] = []
var prefetchItems: [PrefetchMediaItem] = []
switch stickerPack {
case let .result(_, items, _):
var animatedEmojiStickers: [String: StickerPackItem] = [:]
for case let item as StickerPackItem in items {
@ -58,7 +103,7 @@ private final class PrefetchManagerImpl {
animatedEmojiStickers[emoji.basicEmoji.0] = item
}
}
var stickerItems: [PrefetchMediaItem] = []
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"]
for emoji in popularEmoji {
if let sticker = animatedEmojiStickers[emoji] {
@ -68,11 +113,15 @@ private final class PrefetchManagerImpl {
}
}
}
return stickerItems + chatHistoryMediaItems
return stickerItems
default:
return chatHistoryMediaItems
}
break
}
prefetchItems.append(contentsOf: chatHistoryMediaItems)
prefetchItems.append(contentsOf: stickerItems)
return prefetchItems
}
self.listDisposable = (combineLatest(orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)