Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-08-01 16:58:19 +02:00
commit cefc76d4fc
16 changed files with 1280 additions and 78 deletions

View File

@ -14845,7 +14845,7 @@ Sorry for the inconvenience.";
"ProfileLevelInfo.MyDescriptionDays_any" = "%@ days";
"ProfileLevelInfo.MyDescriptionPoints_1" = "%@ point is";
"ProfileLevelInfo.MyDescriptionPoints_any" = "%@ points are";
"ProfileLevelInfo.MyDescription" = "The rating updates in %@ after purchases.\n\%@ pending.";
"ProfileLevelInfo.MyDescriptionPreview" = "The rating updates in %@ after purchases.\n\%@ pending. [Preview >]()";
"ProfileLevelInfo.OtherDescription" = "The rating reflects **%@'s** activity on Telegram. What affects it:";
"ProfileLevelInfo.LevelIndex_1" = "Level %@";
"ProfileLevelInfo.LevelIndex_any" = "Level %@";

View File

@ -2580,7 +2580,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
foundRemotePeers = .single(([], [], [], false))
}
let searchLocations: [SearchMessagesLocation]
if let options = options {
if key == .globalPosts {
searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil)]
} else if let options = options {
if case let .forum(peerId) = location {
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)]
} else if let (peerId, _, _) = options.peer {

View File

@ -783,6 +783,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
let spec: Spec
let backgroundColor: UInt32
let isDark: Bool
let sideInsets: CGFloat
let imageFrame: CGRect
@ -799,6 +800,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
init(
spec: Spec,
backgroundColor: UInt32,
isDark: Bool,
sideInsets: CGFloat,
imageFrame: CGRect,
imageSize: CGSize,
@ -810,6 +812,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
) {
self.spec = spec
self.backgroundColor = backgroundColor
self.isDark = isDark
self.sideInsets = sideInsets
self.imageFrame = imageFrame
self.imageSize = imageSize
@ -853,6 +856,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
}
let backgroundColor = spec.component.chosenOrder != nil ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
let isDark = spec.component.colors.isDark
let imageFrame: CGRect
if spec.component.isTag {
@ -892,7 +896,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
)
}
counterLayout = counterValue
size.width += spacing + counterValue.size.width
if spec.component.count != 0 {
size.width += spacing + counterValue.size.width
} else {
size.width -= 1.0
}
if spec.component.isTag {
size.width += 5.0
}
@ -957,6 +965,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
return Layout(
spec: spec,
backgroundColor: backgroundColor,
isDark: isDark,
sideInsets: sideInsets,
imageFrame: imageFrame,
imageSize: boundingImageSize,
@ -1203,6 +1212,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
let starsEffectLayerFrame = CGRect(origin: CGPoint(), size: layout.size)
animation.animator.updateFrame(layer: starsEffectLayer, frame: starsEffectLayerFrame, completion: nil)
starsEffectLayer.update(size: starsEffectLayerFrame.size)
starsEffectLayer.opacity = layout.isDark ? 0.55 : 1.0
} else {
if let starsEffectLayer = self.starsEffectLayer {
self.starsEffectLayer = nil
@ -1366,6 +1377,7 @@ public final class ReactionButtonComponent: Equatable {
public var extractedSelectedForeground: UInt32
public var deselectedMediaPlaceholder: UInt32
public var selectedMediaPlaceholder: UInt32
public var isDark: Bool
public init(
deselectedBackground: UInt32,
@ -1381,7 +1393,8 @@ public final class ReactionButtonComponent: Equatable {
extractedForeground: UInt32,
extractedSelectedForeground: UInt32,
deselectedMediaPlaceholder: UInt32,
selectedMediaPlaceholder: UInt32
selectedMediaPlaceholder: UInt32,
isDark: Bool
) {
self.deselectedBackground = deselectedBackground
self.selectedBackground = selectedBackground
@ -1397,6 +1410,7 @@ public final class ReactionButtonComponent: Equatable {
self.extractedSelectedForeground = extractedSelectedForeground
self.deselectedMediaPlaceholder = deselectedMediaPlaceholder
self.selectedMediaPlaceholder = selectedMediaPlaceholder
self.isDark = isDark
}
}

View File

@ -73,7 +73,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case keepChatNavigationStack(PresentationTheme, Bool)
case skipReadHistory(PresentationTheme, Bool)
case alwaysDisplayTyping(Bool)
case dustEffect(Bool)
case debugRatingLayout(Bool)
case crashOnSlowQueries(PresentationTheme, Bool)
case crashOnMemoryPressure(PresentationTheme, Bool)
case clearTips(PresentationTheme)
@ -132,7 +132,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue
case .webViewInspection, .resetWebViewCache:
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .debugRatingLayout, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
@ -185,7 +185,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 16
case .alwaysDisplayTyping:
return 17
case .dustEffect:
case .debugRatingLayout:
return 18
case .crashOnSlowQueries:
return 20
@ -970,11 +970,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return settings
}).start()
})
case let .dustEffect(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Dust Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .debugRatingLayout(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Rating Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings
settings.dustEffect = value
settings.debugRatingLayout = value
return settings
}).start()
})
@ -1506,7 +1506,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
#endif
entries.append(.alwaysDisplayTyping(experimentalSettings.alwaysDisplayTyping))
entries.append(.dustEffect(experimentalSettings.dustEffect))
entries.append(.debugRatingLayout(experimentalSettings.debugRatingLayout))
}
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))

View File

@ -532,7 +532,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
@ -548,7 +548,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
@ -570,7 +570,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
@ -586,7 +586,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
@ -605,7 +605,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
@ -621,7 +621,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
reactionActiveForeground: .clear,
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
reactionStarsActiveForeground: .white,
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),

View File

@ -376,7 +376,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
isDark: arguments.presentationData.theme.theme.overallDarkAppearance
)
case .BubbleOutgoing, .ImageOutgoing, .FreeOutgoing:
let themeColors = bubbleColorComponents(theme: arguments.presentationData.theme.theme, incoming: false, wallpaper: !arguments.presentationData.theme.wallpaper.isEmpty)
@ -395,7 +396,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
isDark: arguments.presentationData.theme.theme.overallDarkAppearance
)
}

View File

@ -87,7 +87,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
isDark: presentationData.theme.theme.overallDarkAppearance
)
case .outgoing:
themeColors = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty)
@ -105,7 +106,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
isDark: presentationData.theme.theme.overallDarkAppearance
)
case .freeform:
if presentationData.theme.wallpaper.isEmpty {
@ -128,7 +130,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
extractedSelectedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
isDark: presentationData.theme.theme.overallDarkAppearance
)
}

View File

@ -5,25 +5,134 @@ import ComponentFlow
import MultilineTextComponent
import Svg
private func generateNumberOffsets() -> [CGPoint] {
return [
CGPoint(x: 0.33, y: -0.33),
CGPoint(x: 0.24749999999999983, y: -0.495),
CGPoint(x: 0.0, y: -1.4025),
CGPoint(x: 0.5775, y: -0.495),
CGPoint(x: 0.33, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.49500000000000005, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.165, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.495, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.49500000000000005, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.41250000000000003, y: 0.0),
CGPoint(x: 0.49500000000000005, y: 0.0),
CGPoint(x: 0.2475, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 1.2375, y: 0.0),
CGPoint(x: 1.0725, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 1.32, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 1.5675000000000001, y: 0.0),
CGPoint(x: 0.9075000000000001, y: 0.0),
CGPoint(x: 1.155, y: 0.0),
CGPoint(x: 1.155, y: 0.0),
CGPoint(x: 0.8250000000000001, y: 0.41250000000000003),
CGPoint(x: 0.66, y: 1.32),
CGPoint(x: 0.33, y: 1.32),
CGPoint(x: 0.41250000000000003, y: 0.41250000000000003),
CGPoint(x: 0.49500000000000005, y: 0.2475),
CGPoint(x: 0.41250000000000003, y: 0.33),
CGPoint(x: 0.5775, y: 0.49500000000000005),
CGPoint(x: 0.7425, y: 0.9075000000000001),
CGPoint(x: 0.41250000000000003, y: 0.49500000000000005),
CGPoint(x: 0.5775, y: 0.5775),
CGPoint(x: 1.32, y: -0.9075),
CGPoint(x: 0.49500000000000005, y: -0.41250000000000003),
CGPoint(x: 0.2475, y: -0.9075),
CGPoint(x: 0.7425, y: -0.66),
CGPoint(x: 0.9075000000000001, y: -0.49500000000000005),
CGPoint(x: 0.66, y: -0.165),
CGPoint(x: 1.155, y: -0.16499999999999998),
CGPoint(x: 0.9075000000000001, y: 0.0),
CGPoint(x: 0.8250000000000001, y: 0.0),
CGPoint(x: 0.9900000000000001, y: -0.0825),
CGPoint(x: 0.8250000000000001, y: 0.08249999999999998),
CGPoint(x: 0.41250000000000003, y: 0.0),
CGPoint(x: 0.41250000000000003, y: 0.0),
CGPoint(x: 1.2375, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 1.0725, y: 0.0),
CGPoint(x: 0.8250000000000001, y: 0.0),
CGPoint(x: 0.8250000000000001, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.9075000000000001, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.99, y: 0.0),
CGPoint(x: 0.41250000000000003, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 1.2375, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 1.32, y: 0.0),
CGPoint(x: 1.155, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 1.0725, y: 0.0),
CGPoint(x: 1.2375, y: 0.0),
CGPoint(x: 0.8250000000000001, y: 0.0),
CGPoint(x: 1.0725, y: 0.0),
CGPoint(x: 0.9075000000000001, y: 0.0),
CGPoint(x: 1.155, y: 0.0),
CGPoint(x: 0.8250000000000001, y: 0.0),
CGPoint(x: 1.155, y: 0.0),
CGPoint(x: 1.0725, y: 0.0),
CGPoint(x: 1.2375, y: 0.0),
CGPoint(x: 1.155, y: 0.0),
CGPoint(x: 1.32, y: 0.0),
]
}
let numberOffsets: [CGPoint] = generateNumberOffsets()
public final class PeerInfoRatingComponent: Component {
let backgroundColor: UIColor
let borderColor: UIColor
let foregroundColor: UIColor
let level: Int
let action: () -> Void
let debugLevel: Bool
public init(
backgroundColor: UIColor,
borderColor: UIColor,
foregroundColor: UIColor,
level: Int,
action: @escaping () -> Void
action: @escaping () -> Void,
debugLevel: Bool = false
) {
self.backgroundColor = backgroundColor
self.borderColor = borderColor
self.foregroundColor = foregroundColor
self.level = level
self.action = action
self.debugLevel = debugLevel
}
public static func ==(lhs: PeerInfoRatingComponent, rhs: PeerInfoRatingComponent) -> Bool {
@ -39,6 +148,9 @@ public final class PeerInfoRatingComponent: Component {
if lhs.level != rhs.level {
return false
}
if lhs.debugLevel != rhs.debugLevel {
return false
}
return true
}
@ -56,7 +168,7 @@ public final class PeerInfoRatingComponent: Component {
private let borderLayer: SimpleLayer
private let backgroundLayer: SimpleLayer
//private var tempLevel: Int = 1
private var debugLevel: Int = 1
private var component: PeerInfoRatingComponent?
private weak var state: EmptyComponentState?
@ -78,18 +190,23 @@ public final class PeerInfoRatingComponent: Component {
}
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
self.component?.action()
/*if self.tempLevel < 10 {
self.tempLevel += 1
if component.debugLevel {
if self.debugLevel < 10 {
self.debugLevel += 1
} else {
self.debugLevel += 10
}
if self.debugLevel >= 110 {
self.debugLevel = 1
}
self.state?.updated(transition: .immediate)
} else {
self.tempLevel += 10
self.component?.action()
}
if self.tempLevel >= 110 {
self.tempLevel = 1
}
self.state?.updated(transition: .immediate)*/
}
}
@ -102,14 +219,51 @@ public final class PeerInfoRatingComponent: Component {
self.component = component
self.state = state
let level = component.level
//let level = self.tempLevel
let level: Int
if component.debugLevel {
level = self.debugLevel
} else {
level = component.level
}
let iconSize = CGSize(width: 26.0, height: 26.0)
if previousComponent?.level != level || previousComponent?.borderColor != component.borderColor || previousComponent?.foregroundColor != component.foregroundColor || previousComponent?.backgroundColor != component.backgroundColor || "".isEmpty {
let alwaysRedraw: Bool = component.debugLevel
if previousComponent?.level != level || previousComponent?.borderColor != component.borderColor || previousComponent?.foregroundColor != component.foregroundColor || previousComponent?.backgroundColor != component.backgroundColor || alwaysRedraw {
let weight: CGFloat = UIFont.Weight.semibold.rawValue
let width: CGFloat = -0.1
let descriptor: UIFontDescriptor
if #available(iOS 14.0, *) {
descriptor = UIFont.systemFont(ofSize: 10.0).fontDescriptor
} else {
descriptor = UIFont.systemFont(ofSize: 10.0, weight: UIFont.Weight.semibold).fontDescriptor
}
let symbolicTraits = descriptor.symbolicTraits
var updatedDescriptor: UIFontDescriptor? = descriptor.withSymbolicTraits(symbolicTraits)
updatedDescriptor = updatedDescriptor?.withDesign(.default)
if #available(iOS 14.0, *) {
updatedDescriptor = updatedDescriptor?.addingAttributes([
UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight]
])
}
if #available(iOS 16.0, *) {
updatedDescriptor = updatedDescriptor?.addingAttributes([
UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.width: width]
])
}
let font: UIFont
if let updatedDescriptor {
font = UIFont(descriptor: updatedDescriptor, size: 10.0)
} else {
font = UIFont(descriptor: descriptor, size: 10.0)
}
let attributedText = NSAttributedString(string: "\(level)", attributes: [
NSAttributedString.Key.font: Font.semibold(10.0),
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: component.foregroundColor
])
@ -157,13 +311,25 @@ public final class PeerInfoRatingComponent: Component {
}
let levelIndex: Int
if level <= 10 {
levelIndex = max(0, component.level)
if level < 0 {
levelIndex = 1
} else if level <= 10 {
levelIndex = max(1, level)
} else if level <= 90 {
levelIndex = (level / 10) * 10
} else {
levelIndex = 90
}
let backgroundOffsetsY: [Int: CGFloat] = [
3: -0.8250000000000001,
7: 0.33,
40: 1.4025,
60: 0.2475,
70: 0.33,
80: 0.2475,
]
let borderImage = generateImage(iconSize, rotatedContext: { size, context in
UIGraphicsPushContext(context)
defer {
@ -174,7 +340,7 @@ public final class PeerInfoRatingComponent: Component {
if let url = Bundle.main.url(forResource: "profile_level\(levelIndex)_outer", withExtension: "svg"), let data = try? Data(contentsOf: url) {
if let image = generateTintedImage(image: drawSvgImage(data, size, nil, nil, 0.0, false), color: component.borderColor) {
image.draw(in: CGRect(origin: CGPoint(), size: size), blendMode: .normal, alpha: 1.0)
image.draw(in: CGRect(origin: CGPoint(x: 0.0, y: backgroundOffsetsY[levelIndex] ?? 0.0), size: size), blendMode: .normal, alpha: 1.0)
}
}
})
@ -196,7 +362,7 @@ public final class PeerInfoRatingComponent: Component {
if let url = Bundle.main.url(forResource: "profile_level\(levelIndex)_inner", withExtension: "svg"), let data = try? Data(contentsOf: url) {
if let image = generateTintedImage(image: drawSvgImage(data, size, nil, nil, 0.0, false), color: component.backgroundColor) {
image.draw(in: CGRect(origin: CGPoint(), size: size), blendMode: .normal, alpha: 1.0)
image.draw(in: CGRect(origin: CGPoint(x: 0.0, y: backgroundOffsetsY[levelIndex] ?? 0.0), size: size), blendMode: .normal, alpha: 1.0)
}
}
@ -208,7 +374,15 @@ public final class PeerInfoRatingComponent: Component {
if let textLayout {
let titleScale: CGFloat
if level < 10 {
if level < 0 {
if abs(level) < 10 {
titleScale = 0.8
} else if abs(level) < 100 {
titleScale = 0.6
} else {
titleScale = 0.4
}
} else if level < 10 {
titleScale = 1.0
} else if level < 100 {
titleScale = 0.8
@ -216,19 +390,25 @@ public final class PeerInfoRatingComponent: Component {
titleScale = 0.6
}
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textLayout.size.width) * 0.5), y: floorToScreenPixels((size.height - textLayout.size.height) * 0.5)), size: textLayout.size)
if level == 1 {
textFrame.origin.x += UIScreenPixel
} else {
textFrame.origin.x += 0.0
}
let textFrame = CGRect(origin: CGPoint(x: (size.width - textLayout.size.width) * 0.5, y: (size.height - textLayout.size.height) * 0.5), size: textLayout.size)
context.saveGState()
context.translateBy(x: textFrame.midX, y: textFrame.midY)
context.scaleBy(x: titleScale, y: titleScale)
context.translateBy(x: -textFrame.midX, y: -textFrame.midY)
attributedText.draw(at: textFrame.origin)
var drawPoint: CGPoint
drawPoint = textFrame.origin
if level >= 1 && level <= 99 {
let numberOffset = numberOffsets[level - 1]
drawPoint.x += numberOffset.x
drawPoint.y += numberOffset.y
} else {
drawPoint.x += -UIScreenPixel + -textLayout.opticalBounds.minX + (textFrame.width - textLayout.opticalBounds.width) * 0.5
}
attributedText.draw(at: drawPoint)
context.restoreGState()
}

View File

@ -831,7 +831,36 @@ final class PeerInfoHeaderNode: ASDisplayNode {
headerButtonBackgroundColor = regularHeaderButtonBackgroundColor.mixedWith(collapsedHeaderButtonBackgroundColor, alpha: effectiveTransitionFraction)
if let status = peer?.emojiStatus, case let .starGift(_, _, _, _, _, innerColor, outerColor, _, _) = status.content {
if let status = peer?.emojiStatus, case let .starGift(_, _, _, _, _, innerColor, outerColor, patternColorValue, _) = status.content {
let _ = innerColor
ratingBackgroundColor = UIColor(white: 1.0, alpha: 1.0).mixedWith(presentationData.theme.list.itemCheckColors.fillColor, alpha: effectiveTransitionFraction)
let innerColor = UIColor(rgb: UInt32(bitPattern: innerColor))
let outerColor = UIColor(rgb: UInt32(bitPattern: outerColor))
let backgroundColor = innerColor.mixedWith(outerColor, alpha: 0.8)
let patternColor = UIColor(rgb: UInt32(bitPattern: patternColorValue))
ratingBorderColor = patternColor.withAlphaComponent(0.1).blendOver(background: backgroundColor).mixedWith(.clear, alpha: effectiveTransitionFraction)
ratingForegroundColor = ratingBorderColor.mixedWith(presentationData.theme.list.itemCheckColors.foregroundColor, alpha: effectiveTransitionFraction)
} else if let profileColor = peer?.profileColor {
ratingBackgroundColor = UIColor(white: 1.0, alpha: 1.0).mixedWith(presentationData.theme.list.itemCheckColors.fillColor, alpha: effectiveTransitionFraction)
let backgroundColors = self.context.peerNameColors.getProfile(profileColor, dark: presentationData.theme.overallDarkAppearance)
let innerColor = backgroundColors.main
let outerColor = backgroundColors.secondary ?? backgroundColors.main
let backgroundColor = innerColor.mixedWith(outerColor, alpha: 0.8)
let patternColor = UIColor(white: 0.0, alpha: 0.6)
ratingBorderColor = patternColor.withAlphaComponent(0.1).blendOver(background: backgroundColor).mixedWith(.clear, alpha: effectiveTransitionFraction)
ratingForegroundColor = ratingBorderColor.mixedWith(presentationData.theme.list.itemCheckColors.foregroundColor, alpha: effectiveTransitionFraction)
} else {
ratingBackgroundColor = presentationData.theme.list.itemCheckColors.fillColor
ratingBorderColor = UIColor.clear
ratingForegroundColor = presentationData.theme.list.itemCheckColors.foregroundColor
}
/*if let status = peer?.emojiStatus, case let .starGift(_, _, _, _, _, innerColor, outerColor, _, _) = status.content {
let _ = outerColor
let mainColor = UIColor(rgb: UInt32(bitPattern: innerColor))
@ -848,7 +877,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
ratingBackgroundColor = presentationData.theme.list.itemCheckColors.fillColor
ratingBorderColor = UIColor.clear
ratingForegroundColor = presentationData.theme.list.itemCheckColors.foregroundColor
}
}*/
}
do {
@ -1959,7 +1988,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.currentPendingStarRating = cachedData.pendingStarRating
#if DEBUG
self.currentPendingStarRating = TelegramStarPendingRating(rating: TelegramStarRating(level: starRating.level, currentLevelStars: starRating.currentLevelStars, stars: starRating.stars + 123, nextLevelStars: starRating.nextLevelStars), timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 3)
if let _ = starRating.nextLevelStars {
self.currentPendingStarRating = TelegramStarPendingRating(rating: TelegramStarRating(level: starRating.level, currentLevelStars: starRating.currentLevelStars, stars: starRating.stars + 234, nextLevelStars: starRating.nextLevelStars), timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 3)
self.currentPendingStarRating = TelegramStarPendingRating(rating: TelegramStarRating(level: starRating.level + 1, currentLevelStars: starRating.nextLevelStars!, stars: starRating.nextLevelStars! + starRating.nextLevelStars! / 2, nextLevelStars: starRating.nextLevelStars! * 2), timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 3)
}
#endif
} else {
self.currentStarRating = nil
@ -1996,7 +2028,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
pendingStarRating: self.currentPendingStarRating,
customTheme: self.presentationData?.theme
))
}
},
debugLevel: self.context.sharedContext.immediateExperimentalUISettings.debugRatingLayout
)),
environment: {},
containerSize: CGSize(width: width - 12.0 * 2.0, height: 100.0)

View File

@ -2599,6 +2599,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if let listSource = self.listSource as? PeerStoryListContext {
let _ = listSource.addToFolder(id: folderPreview.folder.id, items: [item])
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let text: String
text = "Story added to folder."
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: text, cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
})))
}
@ -2989,6 +2995,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.initialStoryFolderId = nil
self.setStoryFolder(id: folder.id, assumeEmpty: false, animated: false)
} else {
if self.initialStoryFolderId != nil && !storyFolders.isEmpty {
self.initialStoryFolderId = nil
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "The album is no longer available.", cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
self.currentListState = state
var hasLocalItems = false
@ -3923,21 +3937,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.presentRenameStoryFolder(id: folder.id, title: folder.title)
})
})))
}
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuShare, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
guard let self else {
c?.dismiss(completion: nil)
return
}
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuShare, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c?.dismiss(completion: { [weak self] in
guard let self else {
c?.dismiss(completion: nil)
return
}
c?.dismiss(completion: { [weak self] in
guard let self else {
return
}
self.shareFolder(id: folder.id)
})
})))
}
self.shareFolder(id: folder.id)
})
})))
if self.canManageStories {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
@ -5095,6 +5109,16 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
if let listSource = self.listSource as? PeerStoryListContext {
let _ = listSource.addToFolder(id: folderId, items: items)
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let text: String
if items.count == 1 {
text = "Story added to folder."
} else {
text = "\(items.count) stories added to folder."
}
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: text, cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
})
controller.navigationPresentation = .modal
@ -5392,6 +5416,64 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
collectibleItemInfo: nil
)
self.parentController?.present(shareController, in: .window(.root))
shareController.completed = { [weak self] peerIds in
guard let self else {
return
}
let _ = (self.context.engine.data.get(
EngineDataList(
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in
guard let self else {
return
}
let peers = peerList.compactMap { $0 }
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let text: String
var savedMessages = false
if peers.count == 1, let peer = peers.first {
let peerName = peer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.WebBrowser_LinkForwardTooltip_Chat_One(peerName).string
savedMessages = peer.id == self.context.account.peerId
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.WebBrowser_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.WebBrowser_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
} else {
text = ""
}
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
if savedMessages, let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
guard let navigationController = self.parentController?.navigationController as? NavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
})
}
return false
}), in: .current)
})
}
shareController.actionCompleted = { [weak self] in
guard let self else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}
}
}
}

View File

@ -27,6 +27,9 @@ swift_library(
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/PremiumUI",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
"//submodules/Components/HierarchyTrackingLayer",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
],
visibility = [
"//visibility:public",

View File

@ -142,7 +142,7 @@ private final class ProfileLevelInfoScreenComponent: Component {
self.addSubview(self.dimView)
self.layer.addSublayer(self.backgroundLayer)
self.scrollView.delaysContentTouches = true
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false
self.scrollView.contentInsetAdjustmentBehavior = .never
@ -283,6 +283,8 @@ private final class ProfileLevelInfoScreenComponent: Component {
self.isUpdating = false
}
let isChangingPreview = transition.userData(TransitionHint.self)?.isChangingPreview ?? false
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.16)
let environment = environment[ViewControllerComponentContainer.Environment.self].value
@ -354,13 +356,14 @@ private final class ProfileLevelInfoScreenComponent: Component {
let pendingPoints = pendingStarRating.rating.stars - component.starRating.stars
if self.isPreviewingPendingRating {
//TODO:localize
secondaryDescriptionTextString = "This will be your rating in 21 days,\n after \(pendingPoints) points are added. [Back >]()"
} else {
let dayCount = (pendingStarRating.timestamp - timestamp) / (24 * 60 * 60)
if dayCount == 0 {
secondaryDescriptionTextString = environment.strings.ProfileLevelInfo_MyDescriptionToday(Int32(pendingPoints))
} else {
secondaryDescriptionTextString = environment.strings.ProfileLevelInfo_MyDescription(environment.strings.ProfileLevelInfo_MyDescriptionDays(Int32(dayCount)), environment.strings.ProfileLevelInfo_MyDescriptionPoints(Int32(pendingPoints))).string
secondaryDescriptionTextString = environment.strings.ProfileLevelInfo_MyDescriptionPreview(environment.strings.ProfileLevelInfo_MyDescriptionDays(Int32(dayCount)), environment.strings.ProfileLevelInfo_MyDescriptionPoints(Int32(pendingPoints))).string
}
}
}
@ -398,8 +401,9 @@ private final class ProfileLevelInfoScreenComponent: Component {
environment.theme.list.itemCheckColors.fillColor,
environment.theme.list.itemCheckColors.fillColor
]
let _ = gradientColors
let levelFraction: CGFloat
var levelFraction: CGFloat
let badgeText: String
var badgeTextSuffix: String?
@ -413,8 +417,8 @@ private final class ProfileLevelInfoScreenComponent: Component {
if let nextLevelStars = pendingStarRating.rating.nextLevelStars {
badgeTextSuffix = " / \(starCountString(Int64(nextLevelStars), decimalSeparator: "."))"
}
if let nextLevelStars = pendingStarRating.rating.nextLevelStars {
levelFraction = Double(pendingStarRating.rating.stars) / Double(nextLevelStars)
if let nextLevelStars = pendingStarRating.rating.nextLevelStars, nextLevelStars > pendingStarRating.rating.stars {
levelFraction = Double(pendingStarRating.rating.stars - pendingStarRating.rating.currentLevelStars) / Double(nextLevelStars - pendingStarRating.rating.currentLevelStars)
} else {
levelFraction = 1.0
}
@ -426,13 +430,17 @@ private final class ProfileLevelInfoScreenComponent: Component {
badgeTextSuffix = " / \(starCountString(Int64(nextLevelStars), decimalSeparator: "."))"
}
if let nextLevelStars = component.starRating.nextLevelStars {
levelFraction = Double(component.starRating.stars) / Double(nextLevelStars)
} else {
levelFraction = Double(component.starRating.stars - component.starRating.currentLevelStars) / Double(nextLevelStars - component.starRating.currentLevelStars)
} else if component.starRating.stars > 0 {
levelFraction = 1.0
} else {
levelFraction = 0.0
}
}
levelFraction = max(0.0, levelFraction)
let levelInfoSize = self.levelInfo.update(
/*let levelInfoSize = self.levelInfo.update(
transition: .immediate,
component: AnyComponent(PremiumLimitDisplayComponent(
inactiveColor: environment.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
@ -453,18 +461,31 @@ private final class ProfileLevelInfoScreenComponent: Component {
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 200.0)
)*/
let _ = levelFraction
let levelInfoSize = self.levelInfo.update(
transition: isChangingPreview ? ComponentTransition.immediate.withUserData(ProfileLevelRatingBarComponent.TransitionHint(animate: true)) : .immediate,
component: AnyComponent(ProfileLevelRatingBarComponent(
theme: environment.theme,
value: levelFraction,
leftLabel: environment.strings.ProfileLevelInfo_LevelIndex(Int32(currentLevel)),
rightLabel: nextLevel.flatMap { environment.strings.ProfileLevelInfo_LevelIndex(Int32($0)) } ?? "",
badgeValue: badgeText,
badgeTotal: badgeTextSuffix,
level: Int(currentLevel)
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 110.0)
)
if let levelInfoView = self.levelInfo.view {
if levelInfoView.superview == nil {
self.scrollContentView.addSubview(levelInfoView)
}
levelInfoView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - levelInfoSize.width) * 0.5), y: contentHeight - 16.0), size: levelInfoSize)
levelInfoView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - levelInfoSize.width) * 0.5), y: contentHeight - 6.0), size: levelInfoSize)
}
contentHeight += 129.0
let isChangingPreview = transition.userData(TransitionHint.self)?.isChangingPreview ?? false
if let secondaryDescriptionTextString {
if isChangingPreview, let secondaryDescriptionTextView = self.secondaryDescriptionText?.view {
self.secondaryDescriptionText = nil

View File

@ -0,0 +1,439 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import RoundedRectWithTailPath
import AnimatedTextComponent
import MultilineTextComponent
final class ProfileLevelRatingBarBadge: Component {
final class TransitionHint {
let animateText: Bool
init(animateText: Bool) {
self.animateText = animateText
}
}
let theme: PresentationTheme
let title: String
let suffix: String?
init(
theme: PresentationTheme,
title: String,
suffix: String?
) {
self.theme = theme
self.title = title
self.suffix = suffix
}
static func ==(lhs: ProfileLevelRatingBarBadge, rhs: ProfileLevelRatingBarBadge) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.suffix != rhs.suffix {
return false
}
return true
}
final class View: UIView {
private let badgeView: UIView
private let badgeMaskView: UIView
private let badgeShapeLayer = SimpleShapeLayer()
private let badgeForeground: SimpleLayer
let badgeIcon: UIImageView
private let badgeLabel = ComponentView<Empty>()
private let suffixLabel = ComponentView<Empty>()
private var badgeTailPosition: CGFloat = 0.0
private var badgeShapeArguments: (Double, Double, CGSize, CGFloat, CGFloat)?
private var component: ProfileLevelRatingBarBadge?
private var isUpdating: Bool = false
private var previousAvailableSize: CGSize?
override init(frame: CGRect) {
self.badgeView = UIView()
self.badgeView.alpha = 0.0
self.badgeShapeLayer.fillColor = UIColor.white.cgColor
self.badgeShapeLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
self.badgeMaskView = UIView()
self.badgeMaskView.layer.addSublayer(self.badgeShapeLayer)
self.badgeView.mask = self.badgeMaskView
self.badgeForeground = SimpleLayer()
self.badgeForeground.anchorPoint = CGPoint()
self.badgeIcon = UIImageView()
self.badgeIcon.contentMode = .center
super.init(frame: frame)
self.addSubview(self.badgeView)
self.badgeView.layer.addSublayer(self.badgeForeground)
self.badgeView.addSubview(self.badgeIcon)
self.isUserInteractionEnabled = false
}
required init(coder: NSCoder) {
preconditionFailure()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.badgeView.frame.contains(point) {
return self
} else {
return nil
}
}
func update(component: ProfileLevelRatingBarBadge, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
if self.component == nil {
self.badgeIcon.image = UIImage(bundleImageName: "Peer Info/ProfileLevelProgressIcon")?.withRenderingMode(.alwaysTemplate)
}
self.component = component
self.badgeIcon.tintColor = component.theme.list.itemCheckColors.foregroundColor
var labelsTransition = transition
if let hint = transition.userData(TransitionHint.self), hint.animateText {
labelsTransition = .spring(duration: 0.4)
}
let badgeLabelSize = self.badgeLabel.update(
transition: labelsTransition,
component: AnyComponent(AnimatedTextComponent(
font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []),
color: component.theme.list.itemCheckColors.foregroundColor,
items: [AnimatedTextComponent.Item(
id: AnyHashable(0),
content: .text(component.title)
)]
)),
environment: {},
containerSize: CGSize(width: 300.0, height: 100.0)
)
let badgeSuffixSpacing: CGFloat = 0.0
let badgeSuffixSize = self.suffixLabel.update(
transition: labelsTransition,
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(22.0),
color: component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.6),
items: [AnimatedTextComponent.Item(
id: AnyHashable(0),
content: .text(component.suffix ?? "")
)]
)),
environment: {},
containerSize: CGSize(width: 300.0, height: 100.0)
)
var badgeWidth: CGFloat = badgeLabelSize.width + 3.0 + 54.0
if component.suffix != nil {
badgeWidth += badgeSuffixSize.width + badgeSuffixSpacing
}
let badgeSize = CGSize(width: badgeWidth, height: 48.0)
let badgeFullSize = CGSize(width: badgeWidth, height: 48.0 + 12.0)
self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeFullSize)
self.badgeShapeLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -4.0), size: badgeFullSize)
self.badgeView.bounds = CGRect(origin: .zero, size: badgeFullSize)
self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 600.0, height: badgeFullSize.height + 10.0))
self.badgeIcon.frame = CGRect(x: 10.0, y: 8.0, width: 30.0, height: 30.0)
self.badgeView.alpha = 1.0
let size = badgeSize
var badgeContentWidth: CGFloat = badgeLabelSize.width
if component.suffix != nil {
badgeContentWidth += badgeSuffixSpacing + badgeSuffixSize.width
}
let badgeLabelFrame = CGRect(origin: CGPoint(x: 14.0 + floorToScreenPixels((badgeFullSize.width - badgeContentWidth) / 2.0), y: 9.0), size: badgeLabelSize)
if let badgeLabelView = self.badgeLabel.view {
if badgeLabelView.superview == nil {
self.badgeView.addSubview(badgeLabelView)
}
labelsTransition.setFrame(view: badgeLabelView, frame: badgeLabelFrame)
}
if let suffixLabelView = self.suffixLabel.view {
if suffixLabelView.superview == nil {
suffixLabelView.layer.anchorPoint = CGPoint()
self.badgeView.addSubview(suffixLabelView)
}
let badgeSuffixFrame = CGRect(origin: CGPoint(x: badgeLabelFrame.maxX + badgeSuffixSpacing, y: badgeLabelFrame.maxY - badgeSuffixSize.height), size: badgeSuffixSize)
labelsTransition.setPosition(view: suffixLabelView, position: badgeSuffixFrame.origin)
suffixLabelView.bounds = CGRect(origin: CGPoint(), size: badgeSuffixFrame.size)
}
if self.previousAvailableSize != availableSize {
self.previousAvailableSize = availableSize
let activeColors: [UIColor] = [
component.theme.list.itemCheckColors.fillColor,
component.theme.list.itemCheckColors.fillColor
]
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(activeColors.count - 1)
for i in 0 ..< activeColors.count {
locations.append(delta * CGFloat(i))
}
let gradient = generateGradientImage(size: CGSize(width: 200.0, height: 60.0), colors: activeColors, locations: locations, direction: .horizontal)
self.badgeForeground.contentsGravity = .resizeAspectFill
self.badgeForeground.contents = gradient?.cgImage
self.setupGradientAnimations()
}
return size
}
func adjustTail(size: CGSize, overflowWidth: CGFloat, transition: ComponentTransition) {
var tailPosition = size.width * 0.5
tailPosition += overflowWidth
tailPosition = max(36.0, min(size.width - 36.0, tailPosition))
let tailPositionFraction = tailPosition / size.width
transition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: size, tailPosition: tailPositionFraction, transformTail: false).cgPath)
let transition: ContainedViewLayoutTransition = .immediate
transition.updateAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: tailPositionFraction, y: 1.0))
transition.updatePosition(layer: self.badgeView.layer, position: CGPoint(x: (tailPositionFraction - 0.5) * size.width, y: 0.0))
}
func updateBadgeAngle(angle: CGFloat) {
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformRotation(view: self.badgeView, angle: angle)
}
private func setupGradientAnimations() {
guard let _ = self.component else {
return
}
if let _ = self.badgeForeground.animation(forKey: "movement") {
} else {
CATransaction.begin()
let badgePreviousValue = self.badgeForeground.position.x
let badgeNewValue: CGFloat
if self.badgeForeground.position.x == -300.0 {
badgeNewValue = 0.0
} else {
badgeNewValue = -300.0
}
self.badgeForeground.position = CGPoint(x: badgeNewValue, y: 0.0)
let badgeAnimation = CABasicAnimation(keyPath: "position.x")
badgeAnimation.duration = 4.5
badgeAnimation.fromValue = badgePreviousValue
badgeAnimation.toValue = badgeNewValue
badgeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
CATransaction.setCompletionBlock { [weak self] in
self?.setupGradientAnimations()
}
self.badgeForeground.add(badgeAnimation, forKey: "movement")
CATransaction.commit()
}
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private let labelWidth: CGFloat = 16.0
private let labelHeight: CGFloat = 36.0
private let labelSize = CGSize(width: labelWidth, height: labelHeight)
private let font = Font.with(size: 24.0, design: .round, weight: .semibold, traits: [])
private final class BadgeLabelView: UIView {
private class StackView: UIView {
var labels: [UILabel] = []
var currentValue: Int32 = 0
var color: UIColor = .white {
didSet {
for view in self.labels {
view.textColor = self.color
}
}
}
init() {
super.init(frame: CGRect(origin: .zero, size: labelSize))
var height: CGFloat = -labelHeight
for i in -1 ..< 10 {
let label = UILabel()
if i == -1 {
label.text = "9"
} else {
label.text = "\(i)"
}
label.textColor = self.color
label.font = font
label.textAlignment = .center
label.frame = CGRect(x: 0, y: height, width: labelWidth, height: labelHeight)
self.addSubview(label)
self.labels.append(label)
height += labelHeight
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(value: Int32, isFirst: Bool, isLast: Bool, transition: ComponentTransition) {
let previousValue = self.currentValue
self.currentValue = value
self.labels[1].alpha = isFirst && !isLast ? 0.0 : 1.0
if previousValue == 9 && value < 9 {
self.bounds = CGRect(
origin: CGPoint(
x: 0.0,
y: -1.0 * labelSize.height
),
size: labelSize
)
}
let bounds = CGRect(
origin: CGPoint(
x: 0.0,
y: CGFloat(value) * labelSize.height
),
size: labelSize
)
transition.setBounds(view: self, bounds: bounds)
}
}
private var itemViews: [Int: StackView] = [:]
private var staticLabel = UILabel()
init() {
super.init(frame: .zero)
self.clipsToBounds = true
self.isUserInteractionEnabled = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var color: UIColor = .white {
didSet {
self.staticLabel.textColor = self.color
for (_, view) in self.itemViews {
view.color = self.color
}
}
}
func update(value: String, transition: ComponentTransition) -> CGSize {
if value.contains(" ") {
for (_, view) in self.itemViews {
view.isHidden = true
}
if self.staticLabel.superview == nil {
self.staticLabel.textColor = self.color
self.staticLabel.font = font
self.addSubview(self.staticLabel)
}
self.staticLabel.text = value
let size = self.staticLabel.sizeThatFits(CGSize(width: 100.0, height: 100.0))
self.staticLabel.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: labelHeight))
return CGSize(width: ceil(self.staticLabel.bounds.width), height: ceil(self.staticLabel.bounds.height))
}
let string = value
let stringArray = Array(string.map { String($0) }.reversed())
let labelSpacing: CGFloat = 0.0
let totalWidth = CGFloat(stringArray.count) * labelWidth + CGFloat(stringArray.count - 1) * labelSpacing
var validIds: [Int] = []
for i in 0 ..< stringArray.count {
validIds.append(i)
let itemView: StackView
var itemTransition = transition
if let current = self.itemViews[i] {
itemView = current
} else {
itemTransition = transition.withAnimation(.none)
itemView = StackView()
itemView.color = self.color
self.itemViews[i] = itemView
self.addSubview(itemView)
}
let digit = Int32(stringArray[i]) ?? 0
itemView.update(value: digit, isFirst: i == stringArray.count - 1, isLast: i == 0, transition: transition)
itemTransition.setFrame(
view: itemView,
frame: CGRect(x: totalWidth - labelWidth * CGFloat(i + 1) + labelSpacing * CGFloat(i), y: 0.0, width: labelWidth, height: labelHeight)
)
}
var removeIds: [Int] = []
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {
removeIds.append(id)
transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in
itemView.removeFromSuperview()
})
}
}
for id in removeIds {
self.itemViews.removeValue(forKey: id)
}
return CGSize(width: totalWidth, height: labelHeight)
}
}

View File

@ -0,0 +1,414 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import MultilineTextComponent
import BundleIconComponent
import HierarchyTrackingLayer
final class ProfileLevelRatingBarComponent: Component {
final class TransitionHint {
let animate: Bool
init(animate: Bool) {
self.animate = animate
}
}
let theme: PresentationTheme
let value: CGFloat
let leftLabel: String
let rightLabel: String
let badgeValue: String
let badgeTotal: String?
let level: Int
init(
theme: PresentationTheme,
value: CGFloat,
leftLabel: String,
rightLabel: String,
badgeValue: String,
badgeTotal: String?,
level: Int
) {
self.theme = theme
self.value = value
self.leftLabel = leftLabel
self.rightLabel = rightLabel
self.badgeValue = badgeValue
self.badgeTotal = badgeTotal
self.level = level
}
static func ==(lhs: ProfileLevelRatingBarComponent, rhs: ProfileLevelRatingBarComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.leftLabel != rhs.leftLabel {
return false
}
if lhs.rightLabel != rhs.rightLabel {
return false
}
if lhs.badgeValue != rhs.badgeValue {
return false
}
if lhs.badgeTotal != rhs.badgeTotal {
return false
}
if lhs.level != rhs.level {
return false
}
return true
}
private final class AnimationState {
let fromValue: CGFloat
let toValue: CGFloat
let fromBadgeSize: CGSize
let startTime: Double
let duration: Double
let isWraparound: Bool
init(fromValue: CGFloat, toValue: CGFloat, fromBadgeSize: CGSize, startTime: Double, duration: Double, isWraparound: Bool) {
self.fromValue = fromValue
self.toValue = toValue
self.fromBadgeSize = fromBadgeSize
self.startTime = startTime
self.duration = duration
self.isWraparound = isWraparound
}
func timeFraction(at timestamp: Double) -> CGFloat {
var fraction = CGFloat((timestamp - self.startTime) / self.duration)
fraction = max(0.0, min(1.0, fraction))
return fraction
}
func fraction(at timestamp: Double) -> CGFloat {
return listViewAnimationCurveSystem(self.timeFraction(at: timestamp))
}
func value(at timestamp: Double) -> CGFloat {
let fraction = self.fraction(at: timestamp)
return (1.0 - fraction) * self.fromValue + fraction * self.toValue
}
func wrapAroundValue(at timestamp: Double, topValue: CGFloat) -> CGFloat {
let fraction = self.fraction(at: timestamp)
if fraction <= 0.5 {
let halfFraction = fraction / 0.5
return (1.0 - halfFraction) * self.fromValue + halfFraction * topValue
} else {
let halfFraction = (fraction - 0.5) / 0.5
return halfFraction * self.toValue
}
}
func badgeSize(at timestamp: Double, endValue: CGSize) -> CGSize {
let fraction = self.fraction(at: timestamp)
return CGSize(
width: (1.0 - fraction) * self.fromBadgeSize.width + fraction * endValue.width,
height: endValue.height
)
}
}
final class View: UIView {
private let barBackground: UIImageView
private let backgroundClippingContainer: UIView
private let foregroundClippingContainer: UIView
private let barForeground: UIImageView
private let backgroundLeftLabel = ComponentView<Empty>()
private let backgroundRightLabel = ComponentView<Empty>()
private let foregroundLeftLabel = ComponentView<Empty>()
private let foregroundRightLabel = ComponentView<Empty>()
private let badge = ComponentView<Empty>()
private var component: ProfileLevelRatingBarComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private var hierarchyTracker: HierarchyTrackingLayer?
private var animationLink: SharedDisplayLinkDriver.Link?
private var animationState: AnimationState?
override init(frame: CGRect) {
self.barBackground = UIImageView()
self.backgroundClippingContainer = UIView()
self.backgroundClippingContainer.clipsToBounds = true
self.foregroundClippingContainer = UIView()
self.foregroundClippingContainer.clipsToBounds = true
self.barForeground = UIImageView()
super.init(frame: frame)
let hierarchyTracker = HierarchyTrackingLayer()
self.hierarchyTracker = hierarchyTracker
self.layer.addSublayer(hierarchyTracker)
self.hierarchyTracker?.isInHierarchyUpdated = { [weak self] value in
guard let self else {
return
}
self.updateAnimations()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
private func updateAnimations() {
if let hierarchyTracker = self.hierarchyTracker, hierarchyTracker.isInHierarchy {
if self.animationState != nil {
if self.animationLink == nil {
self.animationLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in
guard let self else {
return
}
self.updateAnimations()
})
}
} else {
self.animationLink?.invalidate()
self.animationLink = nil
self.animationState = nil
}
} else {
self.animationLink?.invalidate()
self.animationLink = nil
self.animationState = nil
}
if let animationState = self.animationState {
if animationState.timeFraction(at: CACurrentMediaTime()) >= 1.0 {
self.animationState = nil
self.updateAnimations()
}
}
if self.animationState != nil && !self.isUpdating {
self.state?.updated(transition: .immediate, isLocal: true)
}
}
func update(component: ProfileLevelRatingBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let barHeight: CGFloat = 30.0
self.isUpdating = true
defer {
self.isUpdating = false
}
var labelsTransition = transition
if let previousComponent = self.component, let hint = transition.userData(TransitionHint.self), hint.animate {
labelsTransition = .spring(duration: 0.4)
let fromValue: CGFloat
if let animationState = self.animationState {
fromValue = animationState.value(at: CACurrentMediaTime())
} else {
fromValue = previousComponent.value
}
let fromBadgeSize: CGSize
if let badgeView = self.badge.view as? ProfileLevelRatingBarBadge.View {
fromBadgeSize = badgeView.bounds.size
} else {
fromBadgeSize = CGSize()
}
self.animationState = AnimationState(
fromValue: fromValue,
toValue: component.value,
fromBadgeSize: fromBadgeSize,
startTime: CACurrentMediaTime(),
duration: 0.4 * UIView.animationDurationFactor(),
isWraparound: false//previousComponent.level < component.level
)
self.updateAnimations()
}
self.component = component
self.state = state
if self.barBackground.image == nil {
self.barBackground.image = generateStretchableFilledCircleImage(diameter: 12.0, color: .white)?.withRenderingMode(.alwaysTemplate)
self.barForeground.image = self.barBackground.image
}
self.barBackground.tintColor = component.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5)
self.barForeground.tintColor = component.theme.list.itemCheckColors.fillColor
if self.barBackground.superview == nil {
self.addSubview(self.barBackground)
self.addSubview(self.backgroundClippingContainer)
self.addSubview(self.foregroundClippingContainer)
self.foregroundClippingContainer.addSubview(self.barForeground)
}
let progressValue: CGFloat
if let animationState = self.animationState {
progressValue = animationState.value(at: CACurrentMediaTime())
} else {
progressValue = component.value
}
let barBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - barHeight), size: CGSize(width: availableSize.width, height: barHeight))
transition.setFrame(view: self.barBackground, frame: barBackgroundFrame)
let barForegroundFrame = CGRect(origin: barBackgroundFrame.origin, size: CGSize(width: floorToScreenPixels(progressValue * barBackgroundFrame.width), height: barBackgroundFrame.height))
var barApparentForegroundFrame = barForegroundFrame
if let animationState = self.animationState, animationState.isWraparound {
let progressValue = animationState.wrapAroundValue(at: CACurrentMediaTime(), topValue: 1.0)
barApparentForegroundFrame = CGRect(origin: barBackgroundFrame.origin, size: CGSize(width: floorToScreenPixels(progressValue * barBackgroundFrame.width), height: barBackgroundFrame.height))
}
transition.setFrame(view: self.foregroundClippingContainer, frame: barApparentForegroundFrame)
let backgroundClippingFrame = CGRect(origin: CGPoint(x: barBackgroundFrame.minX + barApparentForegroundFrame.width, y: barBackgroundFrame.minY), size: CGSize(width: barBackgroundFrame.width - barApparentForegroundFrame.width, height: barBackgroundFrame.height))
transition.setPosition(view: self.backgroundClippingContainer, position: backgroundClippingFrame.center)
transition.setBounds(view: self.backgroundClippingContainer, bounds: CGRect(origin: CGPoint(x: backgroundClippingFrame.minX - barBackgroundFrame.minX, y: 0.0), size: backgroundClippingFrame.size))
transition.setFrame(view: self.barForeground, frame: CGRect(origin: CGPoint(), size: barBackgroundFrame.size))
let labelFont = Font.semibold(14.0)
let leftLabelSize = self.backgroundLeftLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.leftLabel, font: labelFont, textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: barBackgroundFrame.width, height: 100.0)
)
let _ = self.foregroundLeftLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.leftLabel, font: labelFont, textColor: component.theme.list.itemCheckColors.foregroundColor))
)),
environment: {},
containerSize: CGSize(width: barBackgroundFrame.width, height: 100.0)
)
let rightLabelSize = self.backgroundRightLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.rightLabel, font: labelFont, textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: barBackgroundFrame.width, height: 100.0)
)
let _ = self.foregroundRightLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.rightLabel, font: labelFont, textColor: component.theme.list.itemCheckColors.foregroundColor))
)),
environment: {},
containerSize: CGSize(width: barBackgroundFrame.width, height: 100.0)
)
let leftLabelFrame = CGRect(origin: CGPoint(x: 12.0, y: floorToScreenPixels((barBackgroundFrame.height - leftLabelSize.height) * 0.5)), size: leftLabelSize)
let rightLabelFrame = CGRect(origin: CGPoint(x: barBackgroundFrame.width - 12.0 - rightLabelSize.width, y: floorToScreenPixels((barBackgroundFrame.height - rightLabelSize.height) * 0.5)), size: rightLabelSize)
if let backgroundLeftLabelView = self.backgroundLeftLabel.view {
if backgroundLeftLabelView.superview == nil {
backgroundLeftLabelView.layer.anchorPoint = CGPoint()
self.backgroundClippingContainer.addSubview(backgroundLeftLabelView)
}
transition.setPosition(view: backgroundLeftLabelView, position: leftLabelFrame.origin)
backgroundLeftLabelView.bounds = CGRect(origin: CGPoint(), size: leftLabelFrame.size)
}
if let foregroundLeftLabelView = self.foregroundLeftLabel.view {
if foregroundLeftLabelView.superview == nil {
foregroundLeftLabelView.layer.anchorPoint = CGPoint()
self.foregroundClippingContainer.addSubview(foregroundLeftLabelView)
}
transition.setPosition(view: foregroundLeftLabelView, position: leftLabelFrame.origin)
foregroundLeftLabelView.bounds = CGRect(origin: CGPoint(), size: leftLabelFrame.size)
}
if let backgroundRightLabelView = self.backgroundRightLabel.view {
if backgroundRightLabelView.superview == nil {
backgroundRightLabelView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
self.backgroundClippingContainer.addSubview(backgroundRightLabelView)
}
transition.setPosition(view: backgroundRightLabelView, position: CGPoint(x: rightLabelFrame.maxX, y: rightLabelFrame.minY))
backgroundRightLabelView.bounds = CGRect(origin: CGPoint(), size: rightLabelFrame.size)
}
if let foregroundRightLabelView = self.foregroundRightLabel.view {
if foregroundRightLabelView.superview == nil {
foregroundRightLabelView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
self.foregroundClippingContainer.addSubview(foregroundRightLabelView)
}
transition.setPosition(view: foregroundRightLabelView, position: CGPoint(x: rightLabelFrame.maxX, y: rightLabelFrame.minY))
foregroundRightLabelView.bounds = CGRect(origin: CGPoint(), size: rightLabelFrame.size)
}
let badgeSize = self.badge.update(
transition: transition.withUserData(ProfileLevelRatingBarBadge.TransitionHint(animateText: !labelsTransition.animation.isImmediate)),
component: AnyComponent(ProfileLevelRatingBarBadge(
theme: component.theme,
title: "\(component.badgeValue)",
suffix: component.badgeTotal
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 200.0)
)
var badgeFrame = CGRect(origin: CGPoint(x: barBackgroundFrame.minX + barForegroundFrame.width, y: barBackgroundFrame.minY - 7.0), size: badgeSize)
if let badgeView = self.badge.view as? ProfileLevelRatingBarBadge.View {
if badgeView.superview == nil {
self.addSubview(badgeView)
}
let apparentBadgeSize: CGSize
var apparentBadgeOffset: CGFloat = 0.0
if let animationState = self.animationState {
apparentBadgeSize = animationState.badgeSize(at: CACurrentMediaTime(), endValue: badgeSize)
apparentBadgeOffset = (animationState.fromBadgeSize.width - badgeSize.width) * (1.0 - animationState.fraction(at: CACurrentMediaTime()))
apparentBadgeOffset = -apparentBadgeOffset * 0.25
} else {
apparentBadgeSize = badgeSize
}
badgeFrame.size = apparentBadgeSize
let badgeSideInset: CGFloat = 0.0
let badgeOverflowWidth: CGFloat
if badgeFrame.minX - apparentBadgeSize.width * 0.5 < badgeSideInset {
badgeOverflowWidth = badgeSideInset - (badgeFrame.minX - apparentBadgeSize.width * 0.5)
} else if badgeFrame.minX + apparentBadgeSize.width * 0.5 > availableSize.width - badgeSideInset {
badgeOverflowWidth = availableSize.width - badgeSideInset - (badgeFrame.minX + apparentBadgeSize.width * 0.5)
} else {
badgeOverflowWidth = 0.0
}
badgeFrame.origin.x += badgeOverflowWidth + apparentBadgeOffset
badgeView.frame = badgeFrame
badgeView.adjustTail(size: apparentBadgeSize, overflowWidth: -badgeOverflowWidth, transition: transition)
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -3941,6 +3941,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
var switchToRecommendedChannels = false
var switchToGifts = false
var switchToGroupsInCommon = false
var switchToStoryFolder: Int64?
switch mode {
case let .forumTopic(thread):
forumTopicThread = thread
@ -3950,10 +3951,12 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
switchToGifts = true
case .groupsInCommon:
switchToGroupsInCommon = true
case let .storyAlbum(id):
switchToStoryFolder = id
default:
break
}
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGifts: switchToGifts, switchToGroupsInCommon: switchToGroupsInCommon)
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGifts: switchToGifts, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder)
} else if peer is TelegramUser {
var nearbyPeerDistance: Int32?
var reactionSourceMessageId: MessageId?

View File

@ -68,6 +68,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var conferenceDebug: Bool
public var checkSerializedData: Bool
public var allForumsHaveTabs: Bool
public var debugRatingLayout: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -113,7 +114,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
fakeAds: false,
conferenceDebug: false,
checkSerializedData: false,
allForumsHaveTabs: false
allForumsHaveTabs: false,
debugRatingLayout: false
)
}
@ -160,7 +162,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
fakeAds: Bool,
conferenceDebug: Bool,
checkSerializedData: Bool,
allForumsHaveTabs: Bool
allForumsHaveTabs: Bool,
debugRatingLayout: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -205,6 +208,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.conferenceDebug = conferenceDebug
self.checkSerializedData = checkSerializedData
self.allForumsHaveTabs = allForumsHaveTabs
self.debugRatingLayout = debugRatingLayout
}
public init(from decoder: Decoder) throws {
@ -253,6 +257,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.conferenceDebug = try container.decodeIfPresent(Bool.self, forKey: "conferenceDebug") ?? false
self.checkSerializedData = try container.decodeIfPresent(Bool.self, forKey: "checkSerializedData") ?? false
self.allForumsHaveTabs = try container.decodeIfPresent(Bool.self, forKey: "allForumsHaveTabs") ?? false
self.debugRatingLayout = try container.decodeIfPresent(Bool.self, forKey: "debugRatingLayout") ?? false
}
public func encode(to encoder: Encoder) throws {
@ -301,6 +306,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encodeIfPresent(self.conferenceDebug, forKey: "conferenceDebug")
try container.encodeIfPresent(self.checkSerializedData, forKey: "checkSerializedData")
try container.encodeIfPresent(self.allForumsHaveTabs, forKey: "allForumsHaveTabs")
try container.encodeIfPresent(self.debugRatingLayout, forKey: "debugRatingLayout")
}
}