diff --git a/build-system/GenerateStrings/GenerateStrings.py b/build-system/GenerateStrings/GenerateStrings.py index 52a54af3b1..be3033ae34 100644 --- a/build-system/GenerateStrings/GenerateStrings.py +++ b/build-system/GenerateStrings/GenerateStrings.py @@ -243,7 +243,7 @@ def generate(header_path: str, implementation_path: str, data_path: str, entries formatted_accessors += ''' static _FormattedString * _Nonnull getFormatted{num_arguments}(_PresentationStrings * _Nonnull strings, uint32_t keyId{arguments_string}) {{ - NSString *formatString = getSingle(strings, strings->_idToKey[@(keyId)]); + NSString *formatString = getSingle(strings, strings->_idToKey[@(keyId)], nil); NSArray<_FormattedStringRange *> *argumentRanges = extractArgumentRanges(formatString); return formatWithArgumentRanges(formatString, argumentRanges, @[{arguments_array}]); }} @@ -421,8 +421,8 @@ static _FormattedString * _Nonnull formatWithArgumentRanges( return [[_FormattedString alloc] initWithString:result ranges:resultingRanges]; } -static NSString * _Nonnull getPluralizationSuffix(_PresentationStrings * _Nonnull strings, int32_t value) { - NumberPluralizationForm pluralizationForm = numberPluralizationForm(strings.lc, value); +static NSString * _Nonnull getPluralizationSuffix(uint32_t lc, int32_t value) { + NumberPluralizationForm pluralizationForm = numberPluralizationForm(lc, value); switch (pluralizationForm) { case NumberPluralizationFormZero: { return @"_0"; @@ -445,10 +445,14 @@ static NSString * _Nonnull getPluralizationSuffix(_PresentationStrings * _Nonnul } } -static NSString * _Nonnull getSingle(_PresentationStrings * _Nonnull strings, NSString * _Nonnull key) { - NSString *result = strings.primaryComponent.dict[key]; - if (!result) { - result = strings.secondaryComponent.dict[key]; +static NSString * _Nonnull getSingle(_PresentationStrings * _Nullable strings, NSString * _Nonnull key, + bool * _Nullable isFound) { + NSString *result = nil; + if (strings) { + result = strings.primaryComponent.dict[key]; + if (!result) { + result = strings.secondaryComponent.dict[key]; + } } if (!result) { static NSDictionary *fallbackDict = nil; @@ -472,18 +476,31 @@ static NSString * _Nonnull getSingle(_PresentationStrings * _Nonnull strings, NS } if (!result) { result = key; + if (isFound) { + *isFound = false; + } + } else { + if (isFound) { + *isFound = true; + } } return result; } static NSString * _Nonnull getSingleIndirect(_PresentationStrings * _Nonnull strings, uint32_t keyId) { - return getSingle(strings, strings->_idToKey[@(keyId)]); + return getSingle(strings, strings->_idToKey[@(keyId)], nil); } static NSString * _Nonnull getPluralized(_PresentationStrings * _Nonnull strings, NSString * _Nonnull key, int32_t value) { - NSString *parsedKey = [[NSString alloc] initWithFormat:@"%@%@", key, getPluralizationSuffix(strings, value)]; - NSString *formatString = getSingle(strings, parsedKey); + NSString *parsedKey = [[NSString alloc] initWithFormat:@"%@%@", key, getPluralizationSuffix(strings.lc, value)]; + bool isFound = false; + NSString *formatString = getSingle(strings, parsedKey, &isFound); + if (!isFound) { + // fall back to English + parsedKey = [[NSString alloc] initWithFormat:@"%@%@", key, getPluralizationSuffix(0x656e, value)]; + formatString = getSingle(nil, parsedKey, nil); + } NSString *stringValue = formatNumberWithGroupingSeparator(strings.groupingSeparator, value); NSArray<_FormattedStringRange *> *argumentRanges = extractArgumentRanges(formatString); return formatWithArgumentRanges(formatString, argumentRanges, @[stringValue]).string; diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index be1126ab9f..54443cec7b 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -555,8 +555,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var nextChannelToReadDisplayName: Bool = false private var currentOverscrollExpandProgress: CGFloat = 0.0 private var freezeOverscrollControl: Bool = false + private var freezeOverscrollControlProgress: Bool = false private var feedback: HapticFeedback? var openNextChannelToRead: ((EnginePeer, TelegramEngine.NextUnreadChannelLocation) -> Void)? + private var contentInsetAnimator: DisplayLinkAnimator? private let adMessagesContext: AdMessagesHistoryContext? @@ -1294,9 +1296,32 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { guard let strongSelf = self else { return } - if let nextChannelToRead = strongSelf.nextChannelToRead, strongSelf.currentOverscrollExpandProgress >= 0.99 { - strongSelf.freezeOverscrollControl = true - strongSelf.openNextChannelToRead?(nextChannelToRead.peer, nextChannelToRead.location) + if strongSelf.offerNextChannelToRead, strongSelf.currentOverscrollExpandProgress >= 0.99 { + if let nextChannelToRead = strongSelf.nextChannelToRead { + strongSelf.freezeOverscrollControl = true + strongSelf.openNextChannelToRead?(nextChannelToRead.peer, nextChannelToRead.location) + } else { + strongSelf.freezeOverscrollControlProgress = true + strongSelf.scroller.contentInset = UIEdgeInsets(top: 94.0 + 12.0, left: 0.0, bottom: 0.0, right: 0.0) + Queue.mainQueue().after(0.3, { + let animator = DisplayLinkAnimator(duration: 0.2, from: 1.0, to: 0.0, update: { rawT in + guard let strongSelf = self else { + return + } + let t = listViewAnimationCurveEaseInOut(rawT) + let value = (94.0 + 12.0) * t + strongSelf.scroller.contentInset = UIEdgeInsets(top: value, left: 0.0, bottom: 0.0, right: 0.0) + }, completion: { + guard let strongSelf = self else { + return + } + strongSelf.contentInsetAnimator = nil + strongSelf.scroller.contentInset = UIEdgeInsets() + strongSelf.freezeOverscrollControlProgress = false + }) + strongSelf.contentInsetAnimator = animator + }) + } } } @@ -1396,7 +1421,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) } - let overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.bounds.width, height: 94.0)) + var overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.bounds.width, height: 94.0)) + if self.freezeOverscrollControlProgress { + overscrollFrame.origin.y -= max(0.0, 94.0 - expandDistance) + } + + overscrollView.frame = self.view.convert(overscrollFrame, to: self.view.superview!) let _ = overscrollView.update( transition: .immediate, @@ -1407,7 +1437,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { unreadCount: self.nextChannelToRead?.unreadCount ?? 0, location: self.nextChannelToRead?.location ?? .same, context: self.context, - expandDistance: expandDistance, + expandDistance: self.freezeOverscrollControl ? 94.0 : expandDistance, + freezeProgress: false, absoluteRect: CGRect(origin: CGPoint(x: overscrollFrame.minX, y: self.bounds.height - overscrollFrame.minY), size: overscrollFrame.size), absoluteSize: self.bounds.size, wallpaperNode: chatControllerNode.backgroundNode @@ -1415,7 +1446,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { environment: {}, containerSize: CGSize(width: self.bounds.width, height: 200.0) ) - overscrollView.frame = self.view.convert(overscrollFrame, to: self.view.superview!) } else if let overscrollView = self.overscrollView { self.overscrollView = nil overscrollView.removeFromSuperview() diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 04f6197339..443f73020f 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1738,6 +1738,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus private let backgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let placeholderCalculationTextNode: ImmediateTextNode private let textNode: ImmediateTextNode private let shimmerNode: ShimmerEffectNode private let iconNode: ASImageNode @@ -1772,11 +1773,15 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor self.highlightedBackgroundNode.alpha = 0.0 + self.placeholderCalculationTextNode = ImmediateTextNode() + self.placeholderCalculationTextNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ContextMenuSeen(11), font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) + self.placeholderCalculationTextNode.maximumNumberOfLines = 1 + self.textNode = ImmediateTextNode() self.textNode.isAccessibilityElement = false self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false - self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: presentationData.theme.contextMenu.destructiveColor) + self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) self.textNode.maximumNumberOfLines = 1 self.textNode.alpha = 0.0 @@ -1918,6 +1923,8 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus let textSize = self.textNode.updateLayout(CGSize(width: calculatedWidth - sideInset - rightTextInset - iconSize.width - 4.0, height: .greatestFiniteMagnitude)) + let placeholderTextSize = self.placeholderCalculationTextNode.updateLayout(CGSize(width: calculatedWidth - sideInset - rightTextInset - iconSize.width - 4.0, height: .greatestFiniteMagnitude)) + let combinedTextHeight = textSize.height return (CGSize(width: calculatedWidth, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in self.validLayout = (calculatedWidth: calculatedWidth, size: size) @@ -1928,7 +1935,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus let shimmerHeight: CGFloat = 8.0 - self.shimmerNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: floor((size.height - shimmerHeight) / 2.0)), size: CGSize(width: min(100.0, size.width - 40.0), height: shimmerHeight)) + self.shimmerNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: floor((size.height - shimmerHeight) / 2.0)), size: CGSize(width: placeholderTextSize.width, height: shimmerHeight)) self.shimmerNode.cornerRadius = shimmerHeight / 2.0 let shimmeringForegroundColor = self.presentationData.theme.contextMenu.itemSeparatorColor.blitOver(self.presentationData.theme.list.plainBackgroundColor, alpha: 0.9) let shimmeringColor = self.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.2) diff --git a/submodules/TelegramUI/Sources/ChatOverscrollControl.swift b/submodules/TelegramUI/Sources/ChatOverscrollControl.swift index b642e42e96..78b22afba5 100644 --- a/submodules/TelegramUI/Sources/ChatOverscrollControl.swift +++ b/submodules/TelegramUI/Sources/ChatOverscrollControl.swift @@ -669,6 +669,7 @@ final class OverscrollContentsComponent: Component { let unreadCount: Int let location: TelegramEngine.NextUnreadChannelLocation let expandOffset: CGFloat + let freezeProgress: Bool let absoluteRect: CGRect let absoluteSize: CGSize let wallpaperNode: WallpaperBackgroundNode? @@ -681,6 +682,7 @@ final class OverscrollContentsComponent: Component { unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation, expandOffset: CGFloat, + freezeProgress: Bool, absoluteRect: CGRect, absoluteSize: CGSize, wallpaperNode: WallpaperBackgroundNode? @@ -692,6 +694,7 @@ final class OverscrollContentsComponent: Component { self.unreadCount = unreadCount self.location = location self.expandOffset = expandOffset + self.freezeProgress = freezeProgress self.absoluteRect = absoluteRect self.absoluteSize = absoluteSize self.wallpaperNode = wallpaperNode @@ -719,6 +722,9 @@ final class OverscrollContentsComponent: Component { if lhs.expandOffset != rhs.expandOffset { return false } + if lhs.freezeProgress != rhs.freezeProgress { + return false + } if lhs.absoluteRect != rhs.absoluteRect { return false } @@ -811,7 +817,14 @@ final class OverscrollContentsComponent: Component { let minBackgroundHeight: CGFloat = backgroundWidth + 5.0 let avatarInset: CGFloat = 6.0 - let isFullyExpanded = component.expandOffset >= fullHeight + let apparentExpandOffset: CGFloat + if component.freezeProgress { + apparentExpandOffset = fullHeight + } else { + apparentExpandOffset = component.expandOffset + } + + let isFullyExpanded = apparentExpandOffset >= fullHeight let isFolderMask: Bool switch component.location { @@ -821,20 +834,21 @@ final class OverscrollContentsComponent: Component { isFolderMask = false } - let expandProgress: CGFloat = max(0.1, min(1.0, component.expandOffset / fullHeight)) + let expandProgress: CGFloat = max(0.1, min(1.0, apparentExpandOffset / fullHeight)) + let trueExpandProgress: CGFloat = max(0.1, min(1.0, component.expandOffset / fullHeight)) func interpolate(from: CGFloat, to: CGFloat, value: CGFloat) -> CGFloat { return (1.0 - value) * from + value * to } - let backgroundHeight: CGFloat = interpolate(from: minBackgroundHeight, to: fullHeight, value: expandProgress) + let backgroundHeight: CGFloat = interpolate(from: minBackgroundHeight, to: fullHeight, value: trueExpandProgress) let backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundWidth) / 2.0), y: fullHeight - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight)) - let alphaProgress: CGFloat = max(0.0, min(1.0, component.expandOffset / 10.0)) + let alphaProgress: CGFloat = max(0.0, min(1.0, apparentExpandOffset / 10.0)) let maxAvatarScale: CGFloat = 1.0 - var avatarExpandProgress: CGFloat = max(0.01, min(maxAvatarScale, component.expandOffset / fullHeight)) + var avatarExpandProgress: CGFloat = max(0.01, min(maxAvatarScale, apparentExpandOffset / fullHeight)) avatarExpandProgress *= expandProgress let avatarOffsetProgress = interpolate(from: 0.1, to: 1.0, value: avatarExpandProgress) @@ -978,6 +992,7 @@ final class ChatOverscrollControl: CombinedComponent { let location: TelegramEngine.NextUnreadChannelLocation let context: AccountContext let expandDistance: CGFloat + let freezeProgress: Bool let absoluteRect: CGRect let absoluteSize: CGSize let wallpaperNode: WallpaperBackgroundNode? @@ -990,6 +1005,7 @@ final class ChatOverscrollControl: CombinedComponent { location: TelegramEngine.NextUnreadChannelLocation, context: AccountContext, expandDistance: CGFloat, + freezeProgress: Bool, absoluteRect: CGRect, absoluteSize: CGSize, wallpaperNode: WallpaperBackgroundNode? @@ -1001,6 +1017,7 @@ final class ChatOverscrollControl: CombinedComponent { self.location = location self.context = context self.expandDistance = expandDistance + self.freezeProgress = freezeProgress self.absoluteRect = absoluteRect self.absoluteSize = absoluteSize self.wallpaperNode = wallpaperNode @@ -1028,6 +1045,9 @@ final class ChatOverscrollControl: CombinedComponent { if lhs.expandDistance != rhs.expandDistance { return false } + if lhs.freezeProgress != rhs.freezeProgress { + return false + } if lhs.absoluteRect != rhs.absoluteRect { return false } @@ -1053,6 +1073,7 @@ final class ChatOverscrollControl: CombinedComponent { unreadCount: context.component.unreadCount, location: context.component.location, expandOffset: context.component.expandDistance, + freezeProgress: context.component.freezeProgress, absoluteRect: context.component.absoluteRect, absoluteSize: context.component.absoluteSize, wallpaperNode: context.component.wallpaperNode