diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index ad5138b010..c88d26f161 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index c47806f5f7..61ebffec5f 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -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) diff --git a/submodules/CheckNode/Sources/CheckNode.swift b/submodules/CheckNode/Sources/CheckNode.swift index 77070be2d9..ac23bcfb93 100644 --- a/submodules/CheckNode/Sources/CheckNode.swift +++ b/submodules/CheckNode/Sources/CheckNode.swift @@ -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 + } +} diff --git a/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.swift b/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.swift index 3bfba47274..faadfbde7e 100644 --- a/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.swift +++ b/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.swift @@ -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() + } + } +} diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index f3ca0df511..7c9cc2b888 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index dad11c6bb0..ff916e8672 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index 5c68bf81c1..abef0c6bf8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -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)? 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 } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 6a9e491afc..50852c30ba 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift index fa197e8915..0552a6691a 100644 --- a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift @@ -798,8 +798,9 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe private var animationTimer: SwiftSignalKit.Timer? + private let statusPromise = Promise(nil) var status: Signal { - 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 {