mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Shared media improvements
This commit is contained in:
parent
87890295be
commit
c821cc1ab3
@ -296,6 +296,14 @@
|
||||
"Weekday.Today" = "Today";
|
||||
"Weekday.Yesterday" = "Yesterday";
|
||||
|
||||
"Calendar.ShortMonday" = "M";
|
||||
"Calendar.ShortTuesday" = "T";
|
||||
"Calendar.ShortWednesday" = "W";
|
||||
"Calendar.ShortThursday" = "T";
|
||||
"Calendar.ShortFriday" = "F";
|
||||
"Calendar.ShortSaturday" = "S";
|
||||
"Calendar.ShortSunday" = "S";
|
||||
|
||||
"Time.TodayAt" = "today at %@";
|
||||
"Time.YesterdayAt" = "yesterday at %@";
|
||||
|
||||
@ -6986,3 +6994,33 @@ Sorry for the inconvenience.";
|
||||
"AuthSessions.View.IP" = "IP Address";
|
||||
"AuthSessions.View.TerminateSession" = "Terminate Session";
|
||||
"AuthSessions.View.Logout" = "Log Out";
|
||||
|
||||
"MessageCalendar.Title" = "Calendar";
|
||||
"MessageCalendar.DaysSelectedTitle_1" = "1 day selected";
|
||||
"MessageCalendar.DaysSelectedTitle_any" = "%@ days selected";
|
||||
"MessageCalendar.DeleteConfirmation_1" = "Are you sure you want to delete all messages for the selected day?";
|
||||
"MessageCalendar.DeleteConfirmation_1" = "Are you sure you want to delete all messages for the selected %@ days?";
|
||||
|
||||
"SharedMedia.PhotoCount_1" = "1 photo";
|
||||
"SharedMedia.PhotoCount_any" = "%@ photos";
|
||||
"SharedMedia.VideoCount_1" = "1 video";
|
||||
"SharedMedia.VideoCount_any" = "%@ videos";
|
||||
"SharedMedia.GifCount_1" = "1 gif";
|
||||
"SharedMedia.GifCount_any" = "%@ gifs";
|
||||
"SharedMedia.FileCount_1" = "1 file";
|
||||
"SharedMedia.FileCount_any" = "%@ files";
|
||||
"SharedMedia.MusicCount_1" = "1 music file";
|
||||
"SharedMedia.MusicCount_any" = "%@ music files";
|
||||
"SharedMedia.VoiceMessageCount_1" = "1 voice message";
|
||||
"SharedMedia.VoiceMessageCount_any" = "%@ voice messages";
|
||||
"SharedMedia.LinkCount_1" = "1 link";
|
||||
"SharedMedia.LinkCount_any" = "%@ links";
|
||||
|
||||
"SharedMedia.FastScrollTooltip" = "You can hold and move this bar for faster scrolling";
|
||||
"SharedMedia.CalendarTooltip" = "Tap on this icon for calendar view";
|
||||
|
||||
"SharedMedia.ZoomIn" = "Zoom In";
|
||||
"SharedMedia.ZoomOut" = "Zoom Out";
|
||||
"SharedMedia.ShowCalendar" = "Show Calendar";
|
||||
"SharedMedia.ShowPhotos" = "Show Photos";
|
||||
"SharedMedia.ShowVideos" = "Show Videos";
|
||||
|
@ -151,24 +151,21 @@ private func monthName(index: Int, strings: PresentationStrings) -> String {
|
||||
}
|
||||
|
||||
private func dayName(index: Int, strings: PresentationStrings) -> String {
|
||||
let _ = strings
|
||||
//TODO:localize
|
||||
|
||||
switch index {
|
||||
case 0:
|
||||
return "M"
|
||||
return strings.Calendar_ShortMonday
|
||||
case 1:
|
||||
return "T"
|
||||
return strings.Calendar_ShortTuesday
|
||||
case 2:
|
||||
return "W"
|
||||
return strings.Calendar_ShortWednesday
|
||||
case 3:
|
||||
return "T"
|
||||
return strings.Calendar_ShortThursday
|
||||
case 4:
|
||||
return "F"
|
||||
return strings.Calendar_ShortFriday
|
||||
case 5:
|
||||
return "S"
|
||||
return strings.Calendar_ShortSaturday
|
||||
case 6:
|
||||
return "S"
|
||||
return strings.Calendar_ShortSunday
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@ -1402,8 +1399,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
}
|
||||
|
||||
if let _ = info.canClearForMyself ?? info.canClearForEveryone {
|
||||
//TODO:localize
|
||||
items.append(ActionSheetTextItem(title: "Are you sure you want to delete all messages for the \(selectedCount) selected days?"))
|
||||
items.append(ActionSheetTextItem(title: strongSelf.presentationData.strings.MessageCalendar_DeleteConfirmation(Int32(selectedCount))))
|
||||
|
||||
if let canClearForEveryone = info.canClearForEveryone {
|
||||
let text: String
|
||||
@ -1627,8 +1623,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
}
|
||||
|
||||
private func updateSelectionState() {
|
||||
//TODO:localize
|
||||
var title = "Calendar"
|
||||
var title = self.presentationData.strings.MessageCalendar_Title
|
||||
if let selectionState = self.selectionState, let dayRange = selectionState.dayRange {
|
||||
var selectedCount = 0
|
||||
for i in 0 ..< self.months.count {
|
||||
@ -1643,11 +1638,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
}
|
||||
|
||||
if selectedCount != 0 {
|
||||
if selectedCount == 1 {
|
||||
title = "1 day selected"
|
||||
} else {
|
||||
title = "\(selectedCount) days selected"
|
||||
}
|
||||
title = self.presentationData.strings.MessageCalendar_DaysSelectedTitle(Int32(selectedCount))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1732,8 +1723,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false)
|
||||
//TODO:localize
|
||||
self.navigationItem.setTitle("Calendar", animated: false)
|
||||
self.navigationItem.setTitle(self.presentationData.strings.MessageCalendar_Title, animated: false)
|
||||
|
||||
/*if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.toggleSelectPressed)), animated: false)
|
||||
|
@ -290,3 +290,177 @@ public class InteractiveCheckNode: CheckNode {
|
||||
self.buttonNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
private final class NullActionClass: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
}
|
||||
}
|
||||
|
||||
private let nullAction = NullActionClass()
|
||||
|
||||
public class CheckLayer: CALayer {
|
||||
private var animatingOut = false
|
||||
private var animationProgress: CGFloat = 0.0
|
||||
public var theme: CheckNodeTheme {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
public init(theme: CheckNodeTheme, content: CheckNodeContent = .check) {
|
||||
self.theme = theme
|
||||
self.content = content
|
||||
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
public var content: CheckNodeContent {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
public var selected = false
|
||||
public func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||
guard self.selected != selected else {
|
||||
return
|
||||
}
|
||||
self.selected = selected
|
||||
|
||||
if animated {
|
||||
self.animatingOut = !selected
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! CheckLayer).animationProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! CheckLayer).animationProgress = values!.pointee
|
||||
(node as! CheckLayer).setNeedsDisplay()
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as! POPAnimatableProperty)
|
||||
animation.fromValue = (selected ? 0.0 : 1.0) as NSNumber
|
||||
animation.toValue = (selected ? 1.0 : 0.0) as NSNumber
|
||||
animation.timingFunction = CAMediaTimingFunction(name: selected ? CAMediaTimingFunctionName.easeOut : CAMediaTimingFunctionName.easeIn)
|
||||
animation.duration = selected ? 0.21 : 0.15
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
} else {
|
||||
self.pop_removeAllAnimations()
|
||||
self.animatingOut = false
|
||||
self.animationProgress = selected ? 1.0 : 0.0
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
public func setHighlighted(_ highlighted: Bool, animated: Bool = false) {
|
||||
}
|
||||
|
||||
override public func display() {
|
||||
if self.bounds.isEmpty {
|
||||
return
|
||||
}
|
||||
self.contents = generateImage(self.bounds.size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let parameters = CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
||||
|
||||
let center = CGPoint(x: bounds.width / 2.0, y: bounds.width / 2.0)
|
||||
|
||||
var borderWidth: CGFloat = 1.0 + UIScreenPixel
|
||||
if parameters.theme.hasInset {
|
||||
borderWidth = 1.5
|
||||
}
|
||||
if let customBorderWidth = parameters.theme.borderWidth {
|
||||
borderWidth = customBorderWidth
|
||||
}
|
||||
|
||||
let checkWidth: CGFloat = 1.5
|
||||
|
||||
let inset: CGFloat = parameters.theme.hasInset ? 2.0 - UIScreenPixel : 0.0
|
||||
|
||||
let checkProgress = parameters.animatingOut ? 1.0 : parameters.animationProgress
|
||||
let fillProgress = parameters.animatingOut ? 1.0 : min(1.0, parameters.animationProgress * 1.35)
|
||||
|
||||
context.setStrokeColor(parameters.theme.borderColor.cgColor)
|
||||
context.setLineWidth(borderWidth)
|
||||
|
||||
let maybeScaleOut = {
|
||||
if parameters.animatingOut {
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: parameters.animationProgress, y: parameters.animationProgress)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
context.setAlpha(parameters.animationProgress)
|
||||
}
|
||||
}
|
||||
|
||||
let borderInset = borderWidth / 2.0 + inset
|
||||
let borderProgress: CGFloat = parameters.theme.filledBorder ? fillProgress : 1.0
|
||||
let borderFrame = bounds.insetBy(dx: borderInset, dy: borderInset)
|
||||
|
||||
if parameters.theme.filledBorder {
|
||||
maybeScaleOut()
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
if parameters.theme.hasShadow {
|
||||
context.setShadow(offset: CGSize(), blur: 2.5, color: UIColor(rgb: 0x000000, alpha: 0.22).cgColor)
|
||||
}
|
||||
|
||||
context.strokeEllipse(in: borderFrame.insetBy(dx: borderFrame.width * (1.0 - borderProgress), dy: borderFrame.height * (1.0 - borderProgress)))
|
||||
context.restoreGState()
|
||||
|
||||
if !parameters.theme.filledBorder {
|
||||
maybeScaleOut()
|
||||
}
|
||||
|
||||
context.setFillColor(parameters.theme.backgroundColor.cgColor)
|
||||
|
||||
let fillInset = parameters.theme.overlayBorder ? borderWidth + inset : inset
|
||||
let fillFrame = bounds.insetBy(dx: fillInset, dy: fillInset)
|
||||
context.fillEllipse(in: fillFrame.insetBy(dx: fillFrame.width * (1.0 - fillProgress), dy: fillFrame.height * (1.0 - fillProgress)))
|
||||
|
||||
let scale = (bounds.width - inset) / 18.0
|
||||
|
||||
let firstSegment: CGFloat = max(0.0, min(1.0, checkProgress * 3.0))
|
||||
let s = CGPoint(x: center.x - (4.0 - 0.3333) * scale, y: center.y + 0.5 * scale)
|
||||
let p1 = CGPoint(x: 2.5 * scale, y: 3.0 * scale)
|
||||
let p2 = CGPoint(x: 4.6667 * scale, y: -6.0 * scale)
|
||||
|
||||
if !firstSegment.isZero {
|
||||
if firstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
|
||||
context.addLine(to: s)
|
||||
} else {
|
||||
let secondSegment = (checkProgress - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
|
||||
context.addLine(to: s)
|
||||
}
|
||||
}
|
||||
|
||||
context.setStrokeColor(parameters.theme.strokeColor.cgColor)
|
||||
if parameters.theme.strokeColor == .clear {
|
||||
context.setBlendMode(.clear)
|
||||
}
|
||||
context.setLineWidth(checkWidth)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.setMiterLimit(10.0)
|
||||
|
||||
context.strokePath()
|
||||
})?.cgImage
|
||||
}
|
||||
}
|
||||
|
@ -59,3 +59,59 @@ public final class GridMessageSelectionNode: ASDisplayNode {
|
||||
self.checkNode.frame = CGRect(origin: CGPoint(x: self.bounds.size.width - checkSize.width - 2.0, y: 2.0), size: checkSize)
|
||||
}
|
||||
}
|
||||
|
||||
private final class NullActionClass: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
}
|
||||
}
|
||||
|
||||
private let nullAction = NullActionClass()
|
||||
|
||||
public final class GridMessageSelectionLayer: CALayer {
|
||||
private var selected = false
|
||||
private let checkLayer: CheckLayer
|
||||
|
||||
public init(theme: CheckNodeTheme) {
|
||||
self.checkLayer = CheckLayer(theme: theme, content: .check)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSublayer(self.checkLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
public func animateIn() {
|
||||
self.checkLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.checkLayer.animateScale(from: 0.2, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
public func animateOut(completion: @escaping () -> Void) {
|
||||
self.checkLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.checkLayer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
public func updateSelected(_ selected: Bool, animated: Bool) {
|
||||
if self.selected != selected {
|
||||
self.selected = selected
|
||||
self.checkLayer.setSelected(selected, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
let checkSize = CGSize(width: 28.0, height: 28.0)
|
||||
let previousSize = self.checkLayer.bounds.size
|
||||
self.checkLayer.frame = CGRect(origin: CGPoint(x: self.bounds.size.width - checkSize.width - 2.0, y: 2.0), size: checkSize)
|
||||
if self.checkLayer.bounds.size != previousSize {
|
||||
self.checkLayer.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1214,6 +1214,12 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
public var cancelExternalContentGestures: (() -> Void)?
|
||||
public var zoomLevelUpdated: ((ZoomLevel) -> Void)?
|
||||
|
||||
public var pinchEnabled: Bool = true {
|
||||
didSet {
|
||||
self.pinchRecognizer?.isEnabled = self.pinchEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
|
@ -148,16 +148,17 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
switch tagMask {
|
||||
case MessageTags.file:
|
||||
return PeerInfoStatusData(text: "\(count) files", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_FileCount(Int32(count)), isActivity: false)
|
||||
case MessageTags.music:
|
||||
return PeerInfoStatusData(text: "\(count) music files", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_MusicCount(Int32(count)), isActivity: false)
|
||||
case MessageTags.voiceOrInstantVideo:
|
||||
return PeerInfoStatusData(text: "\(count) voice messages", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_VoiceMessageCount(Int32(count)), isActivity: false)
|
||||
case MessageTags.webPage:
|
||||
return PeerInfoStatusData(text: "\(count) links", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_LinkCount(Int32(count)), isActivity: false)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import DirectMediaImageCache
|
||||
import ComponentFlow
|
||||
import TelegramNotices
|
||||
import TelegramUIPreferences
|
||||
import CheckNode
|
||||
|
||||
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
@ -754,6 +755,7 @@ private final class DurationLayer: CALayer {
|
||||
private final class ItemLayer: CALayer, SparseItemGridLayer {
|
||||
var item: VisualMediaItem?
|
||||
var durationLayer: DurationLayer?
|
||||
var selectionLayer: GridMessageSelectionLayer?
|
||||
var disposable: Disposable?
|
||||
|
||||
var hasContents: Bool = false
|
||||
@ -796,6 +798,35 @@ private final class ItemLayer: CALayer, SparseItemGridLayer {
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) {
|
||||
if let isSelected = isSelected {
|
||||
if let selectionLayer = self.selectionLayer {
|
||||
selectionLayer.updateSelected(isSelected, animated: animated)
|
||||
} else {
|
||||
let selectionLayer = GridMessageSelectionLayer(theme: theme)
|
||||
selectionLayer.updateSelected(isSelected, animated: false)
|
||||
self.selectionLayer = selectionLayer
|
||||
self.addSublayer(selectionLayer)
|
||||
if !self.bounds.isEmpty {
|
||||
selectionLayer.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
selectionLayer.updateLayout(size: self.bounds.size)
|
||||
if animated {
|
||||
selectionLayer.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let selectionLayer = self.selectionLayer {
|
||||
self.selectionLayer = nil
|
||||
if animated {
|
||||
selectionLayer.animateOut { [weak selectionLayer] in
|
||||
selectionLayer?.removeFromSuperlayer()
|
||||
}
|
||||
} else {
|
||||
selectionLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unbind() {
|
||||
self.item = nil
|
||||
}
|
||||
@ -975,6 +1006,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
let listItemInteraction: ListMessageItemInteraction
|
||||
let chatControllerInteraction: ChatControllerInteraction
|
||||
let chatPresentationData: ChatPresentationData
|
||||
let checkNodeTheme: CheckNodeTheme
|
||||
|
||||
var loadHoleImpl: ((SparseItemGrid.HoleAnchor, SparseItemGrid.HoleLocation) -> Signal<Never, NoError>)?
|
||||
var onTapImpl: ((VisualMediaItem) -> Void)?
|
||||
@ -1000,6 +1032,8 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
|
||||
let themeData = ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper)
|
||||
self.chatPresentationData = ChatPresentationData(theme: themeData, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners, animatedEmojiScale: 1.0)
|
||||
|
||||
self.checkNodeTheme = CheckNodeTheme(theme: presentationData.theme, style: .overlay, hasInset: true)
|
||||
}
|
||||
|
||||
func getListShimmerImage(height: CGFloat) -> UIImage {
|
||||
@ -1250,6 +1284,12 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
||||
layer.updateDuration(duration: duration)
|
||||
}
|
||||
|
||||
if let selectionState = self.chatControllerInteraction.selectionState {
|
||||
layer.updateSelection(theme: self.checkNodeTheme, isSelected: selectionState.selectedIds.contains(message.id), animated: false)
|
||||
} else {
|
||||
layer.updateSelection(theme: self.checkNodeTheme, isSelected: nil, animated: false)
|
||||
}
|
||||
|
||||
layer.bind(item: item)
|
||||
}
|
||||
}
|
||||
@ -1484,8 +1524,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
return
|
||||
}
|
||||
if count < 1 {
|
||||
//TODO:localize
|
||||
strongSelf.itemGrid.updateScrollingAreaTooltip(tooltip: SparseItemGridScrollingArea.DisplayTooltip(animation: "anim_infotip", text: "You can hold and move this bar for faster scrolling", completed: {
|
||||
strongSelf.itemGrid.updateScrollingAreaTooltip(tooltip: SparseItemGridScrollingArea.DisplayTooltip(animation: "anim_infotip", text: strongSelf.itemGridBinding.chatPresentationData.strings.SharedMedia_FastScrollTooltip, completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1505,7 +1544,15 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.chatControllerInteraction.openMessage(item.message, .default)
|
||||
if let selectionState = strongSelf.chatControllerInteraction.selectionState {
|
||||
var toggledValue = true
|
||||
if selectionState.selectedIds.contains(item.message.id) {
|
||||
toggledValue = false
|
||||
}
|
||||
strongSelf.chatControllerInteraction.toggleMessagesSelection([item.message.id], toggledValue)
|
||||
} else {
|
||||
let _ = strongSelf.chatControllerInteraction.openMessage(item.message, .default)
|
||||
}
|
||||
}
|
||||
|
||||
self.itemGridBinding.onTagTapImpl = { [weak self] in
|
||||
@ -1813,72 +1860,67 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
return true
|
||||
})
|
||||
|> map { contentType, dict -> PeerInfoStatusData? in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
switch contentType {
|
||||
case .photoOrVideo:
|
||||
let photoCount: Int32 = dict[.photo] ?? 0
|
||||
let videoCount: Int32 = dict[.video] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if photoCount != 0 && videoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) photos, \(videoCount) videos", isActivity: false)
|
||||
return PeerInfoStatusData(text: "\(presentationData.strings.SharedMedia_PhotoCount(Int32(photoCount))), \(presentationData.strings.SharedMedia_VideoCount(Int32(videoCount)))", isActivity: false)
|
||||
} else if photoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) photos", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_PhotoCount(Int32(photoCount)), isActivity: false)
|
||||
} else if videoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(videoCount) videos", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_VideoCount(Int32(videoCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .photo:
|
||||
let photoCount: Int32 = dict[.photo] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if photoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) photos", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_PhotoCount(Int32(photoCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .video:
|
||||
let videoCount: Int32 = dict[.video] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if videoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(videoCount) videos", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_VideoCount(Int32(videoCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .gifs:
|
||||
let gifCount: Int32 = dict[.gif] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if gifCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(gifCount) gifs", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_GifCount(Int32(gifCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .files:
|
||||
let fileCount: Int32 = dict[.file] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if fileCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(fileCount) files", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_FileCount(Int32(fileCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .voiceAndVideoMessages:
|
||||
let itemCount: Int32 = dict[.voiceOrInstantVideo] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if itemCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(itemCount) voice messages", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_VoiceMessageCount(Int32(itemCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .music:
|
||||
let itemCount: Int32 = dict[.music] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if itemCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(itemCount) music files", isActivity: false)
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_MusicCount(Int32(itemCount)), isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -2105,21 +2147,33 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
|
||||
func updateSelectedMessages(animated: Bool) {
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemView = item.view as? ItemView else {
|
||||
return
|
||||
switch self.contentType {
|
||||
case .files, .music, .voiceAndVideoMessages:
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemView = item.view as? ItemView else {
|
||||
return
|
||||
}
|
||||
if let item = itemView.item {
|
||||
itemView.bind(
|
||||
item: item,
|
||||
presentationData: self.itemGridBinding.chatPresentationData,
|
||||
context: self.itemGridBinding.context,
|
||||
chatLocation: self.itemGridBinding.chatLocation,
|
||||
interaction: self.itemGridBinding.listItemInteraction,
|
||||
isSelected: self.chatControllerInteraction.selectionState?.selectedIds.contains(item.message.id),
|
||||
size: itemView.bounds.size
|
||||
)
|
||||
}
|
||||
}
|
||||
if let item = itemView.item {
|
||||
itemView.bind(
|
||||
item: item,
|
||||
presentationData: self.itemGridBinding.chatPresentationData,
|
||||
context: self.itemGridBinding.context,
|
||||
chatLocation: self.itemGridBinding.chatLocation,
|
||||
interaction: self.itemGridBinding.listItemInteraction,
|
||||
isSelected: self.chatControllerInteraction.selectionState?.selectedIds.contains(item.message.id),
|
||||
size: itemView.bounds.size
|
||||
)
|
||||
case .photo, .video, .photoOrVideo, .gifs:
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer, let item = itemLayer.item else {
|
||||
return
|
||||
}
|
||||
itemLayer.updateSelection(theme: self.itemGridBinding.checkNodeTheme, isSelected: self.chatControllerInteraction.selectionState?.selectedIds.contains(item.message.id), animated: animated)
|
||||
}
|
||||
|
||||
self.itemGrid.pinchEnabled = self.chatControllerInteraction.selectionState == nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6061,8 +6061,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
return
|
||||
}
|
||||
let buttonFrame = buttonNode.view.convert(buttonNode.bounds, to: self.view)
|
||||
//TODO:localize
|
||||
controller.present(TooltipScreen(account: self.context.account, text: "Tap on this icon for calendar view", style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in
|
||||
controller.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in
|
||||
return .dismiss(consume: false)
|
||||
}), in: .current)
|
||||
}
|
||||
@ -6099,14 +6098,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
//TODO:localize
|
||||
|
||||
let strings = strongSelf.presentationData.strings
|
||||
|
||||
var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)?
|
||||
let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in
|
||||
let nextZoomLevel = isZoomIn ? pane?.availableZoomLevels().increment : pane?.availableZoomLevels().decrement
|
||||
let canZoom: Bool = nextZoomLevel != nil
|
||||
|
||||
return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? "Zoom In" : "Zoom Out", textColor: canZoom ? .primary : .disabled, icon: { theme in
|
||||
return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? strings.SharedMedia_ZoomIn : strings.SharedMedia_ZoomOut, textColor: canZoom ? .primary : .disabled, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
|
||||
}, action: canZoom ? { action in
|
||||
guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else {
|
||||
@ -6127,7 +6127,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
items.append(.action(generateAction(false)))
|
||||
|
||||
var ignoreNextActions = false
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Calendar", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowCalendar, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, a in
|
||||
if ignoreNextActions {
|
||||
@ -6157,7 +6157,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
showVideos = false
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Photos", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowPhotos, icon: { theme in
|
||||
if !showPhotos {
|
||||
return nil
|
||||
}
|
||||
@ -6181,7 +6181,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
pane.updateContentType(contentType: updatedContentType)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Videos", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowVideos, icon: { theme in
|
||||
if !showVideos {
|
||||
return nil
|
||||
}
|
||||
|
@ -798,8 +798,9 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe
|
||||
|
||||
private var animationTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private let statusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
return .single(nil)
|
||||
self.statusPromise.get()
|
||||
}
|
||||
|
||||
var tabBarOffsetUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
@ -872,6 +873,23 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe
|
||||
}, queue: .mainQueue())
|
||||
self.animationTimer = animationTimer
|
||||
animationTimer.start()
|
||||
|
||||
self.statusPromise.set(context.account.postbox.combinedView(keys: [PostboxViewKey.historyTagSummaryView(tag: tagMaskForType(self.contentType), peerId: peerId, namespace: Namespaces.Message.Cloud)])
|
||||
|> map { views -> PeerInfoStatusData? in
|
||||
let count: Int32 = (views.views[PostboxViewKey.historyTagSummaryView(tag: tagMaskForType(self.contentType), peerId: peerId, namespace: Namespaces.Message.Cloud)] as? MessageHistoryTagSummaryView)?.count ?? 0
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
switch contentType {
|
||||
case .gifs:
|
||||
return PeerInfoStatusData(text: presentationData.strings.SharedMedia_GifCount(Int32(count)), isActivity: false)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
Loading…
x
Reference in New Issue
Block a user