mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
cefc76d4fc
@ -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 %@";
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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?
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user