mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-27 10:32:37 +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.MyDescriptionDays_any" = "%@ days";
|
||||||
"ProfileLevelInfo.MyDescriptionPoints_1" = "%@ point is";
|
"ProfileLevelInfo.MyDescriptionPoints_1" = "%@ point is";
|
||||||
"ProfileLevelInfo.MyDescriptionPoints_any" = "%@ points are";
|
"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.OtherDescription" = "The rating reflects **%@'s** activity on Telegram. What affects it:";
|
||||||
"ProfileLevelInfo.LevelIndex_1" = "Level %@";
|
"ProfileLevelInfo.LevelIndex_1" = "Level %@";
|
||||||
"ProfileLevelInfo.LevelIndex_any" = "Level %@";
|
"ProfileLevelInfo.LevelIndex_any" = "Level %@";
|
||||||
|
|||||||
@ -2580,7 +2580,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
foundRemotePeers = .single(([], [], [], false))
|
foundRemotePeers = .single(([], [], [], false))
|
||||||
}
|
}
|
||||||
let searchLocations: [SearchMessagesLocation]
|
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 {
|
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)]
|
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 {
|
} else if let (peerId, _, _) = options.peer {
|
||||||
|
|||||||
@ -783,6 +783,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
let spec: Spec
|
let spec: Spec
|
||||||
|
|
||||||
let backgroundColor: UInt32
|
let backgroundColor: UInt32
|
||||||
|
let isDark: Bool
|
||||||
let sideInsets: CGFloat
|
let sideInsets: CGFloat
|
||||||
|
|
||||||
let imageFrame: CGRect
|
let imageFrame: CGRect
|
||||||
@ -799,6 +800,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
init(
|
init(
|
||||||
spec: Spec,
|
spec: Spec,
|
||||||
backgroundColor: UInt32,
|
backgroundColor: UInt32,
|
||||||
|
isDark: Bool,
|
||||||
sideInsets: CGFloat,
|
sideInsets: CGFloat,
|
||||||
imageFrame: CGRect,
|
imageFrame: CGRect,
|
||||||
imageSize: CGSize,
|
imageSize: CGSize,
|
||||||
@ -810,6 +812,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
) {
|
) {
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
|
self.isDark = isDark
|
||||||
self.sideInsets = sideInsets
|
self.sideInsets = sideInsets
|
||||||
self.imageFrame = imageFrame
|
self.imageFrame = imageFrame
|
||||||
self.imageSize = imageSize
|
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 backgroundColor = spec.component.chosenOrder != nil ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
|
||||||
|
let isDark = spec.component.colors.isDark
|
||||||
|
|
||||||
let imageFrame: CGRect
|
let imageFrame: CGRect
|
||||||
if spec.component.isTag {
|
if spec.component.isTag {
|
||||||
@ -892,7 +896,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
counterLayout = counterValue
|
counterLayout = counterValue
|
||||||
|
if spec.component.count != 0 {
|
||||||
size.width += spacing + counterValue.size.width
|
size.width += spacing + counterValue.size.width
|
||||||
|
} else {
|
||||||
|
size.width -= 1.0
|
||||||
|
}
|
||||||
if spec.component.isTag {
|
if spec.component.isTag {
|
||||||
size.width += 5.0
|
size.width += 5.0
|
||||||
}
|
}
|
||||||
@ -957,6 +965,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
return Layout(
|
return Layout(
|
||||||
spec: spec,
|
spec: spec,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
|
isDark: isDark,
|
||||||
sideInsets: sideInsets,
|
sideInsets: sideInsets,
|
||||||
imageFrame: imageFrame,
|
imageFrame: imageFrame,
|
||||||
imageSize: boundingImageSize,
|
imageSize: boundingImageSize,
|
||||||
@ -1203,6 +1212,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
let starsEffectLayerFrame = CGRect(origin: CGPoint(), size: layout.size)
|
let starsEffectLayerFrame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
animation.animator.updateFrame(layer: starsEffectLayer, frame: starsEffectLayerFrame, completion: nil)
|
animation.animator.updateFrame(layer: starsEffectLayer, frame: starsEffectLayerFrame, completion: nil)
|
||||||
starsEffectLayer.update(size: starsEffectLayerFrame.size)
|
starsEffectLayer.update(size: starsEffectLayerFrame.size)
|
||||||
|
|
||||||
|
starsEffectLayer.opacity = layout.isDark ? 0.55 : 1.0
|
||||||
} else {
|
} else {
|
||||||
if let starsEffectLayer = self.starsEffectLayer {
|
if let starsEffectLayer = self.starsEffectLayer {
|
||||||
self.starsEffectLayer = nil
|
self.starsEffectLayer = nil
|
||||||
@ -1366,6 +1377,7 @@ public final class ReactionButtonComponent: Equatable {
|
|||||||
public var extractedSelectedForeground: UInt32
|
public var extractedSelectedForeground: UInt32
|
||||||
public var deselectedMediaPlaceholder: UInt32
|
public var deselectedMediaPlaceholder: UInt32
|
||||||
public var selectedMediaPlaceholder: UInt32
|
public var selectedMediaPlaceholder: UInt32
|
||||||
|
public var isDark: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
deselectedBackground: UInt32,
|
deselectedBackground: UInt32,
|
||||||
@ -1381,7 +1393,8 @@ public final class ReactionButtonComponent: Equatable {
|
|||||||
extractedForeground: UInt32,
|
extractedForeground: UInt32,
|
||||||
extractedSelectedForeground: UInt32,
|
extractedSelectedForeground: UInt32,
|
||||||
deselectedMediaPlaceholder: UInt32,
|
deselectedMediaPlaceholder: UInt32,
|
||||||
selectedMediaPlaceholder: UInt32
|
selectedMediaPlaceholder: UInt32,
|
||||||
|
isDark: Bool
|
||||||
) {
|
) {
|
||||||
self.deselectedBackground = deselectedBackground
|
self.deselectedBackground = deselectedBackground
|
||||||
self.selectedBackground = selectedBackground
|
self.selectedBackground = selectedBackground
|
||||||
@ -1397,6 +1410,7 @@ public final class ReactionButtonComponent: Equatable {
|
|||||||
self.extractedSelectedForeground = extractedSelectedForeground
|
self.extractedSelectedForeground = extractedSelectedForeground
|
||||||
self.deselectedMediaPlaceholder = deselectedMediaPlaceholder
|
self.deselectedMediaPlaceholder = deselectedMediaPlaceholder
|
||||||
self.selectedMediaPlaceholder = selectedMediaPlaceholder
|
self.selectedMediaPlaceholder = selectedMediaPlaceholder
|
||||||
|
self.isDark = isDark
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case keepChatNavigationStack(PresentationTheme, Bool)
|
case keepChatNavigationStack(PresentationTheme, Bool)
|
||||||
case skipReadHistory(PresentationTheme, Bool)
|
case skipReadHistory(PresentationTheme, Bool)
|
||||||
case alwaysDisplayTyping(Bool)
|
case alwaysDisplayTyping(Bool)
|
||||||
case dustEffect(Bool)
|
case debugRatingLayout(Bool)
|
||||||
case crashOnSlowQueries(PresentationTheme, Bool)
|
case crashOnSlowQueries(PresentationTheme, Bool)
|
||||||
case crashOnMemoryPressure(PresentationTheme, Bool)
|
case crashOnMemoryPressure(PresentationTheme, Bool)
|
||||||
case clearTips(PresentationTheme)
|
case clearTips(PresentationTheme)
|
||||||
@ -132,7 +132,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .webViewInspection, .resetWebViewCache:
|
case .webViewInspection, .resetWebViewCache:
|
||||||
return DebugControllerSection.web.rawValue
|
return DebugControllerSection.web.rawValue
|
||||||
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .debugRatingLayout, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||||
return DebugControllerSection.experiments.rawValue
|
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:
|
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
|
return DebugControllerSection.experiments.rawValue
|
||||||
@ -185,7 +185,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 16
|
return 16
|
||||||
case .alwaysDisplayTyping:
|
case .alwaysDisplayTyping:
|
||||||
return 17
|
return 17
|
||||||
case .dustEffect:
|
case .debugRatingLayout:
|
||||||
return 18
|
return 18
|
||||||
case .crashOnSlowQueries:
|
case .crashOnSlowQueries:
|
||||||
return 20
|
return 20
|
||||||
@ -970,11 +970,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return settings
|
return settings
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
case let .dustEffect(value):
|
case let .debugRatingLayout(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Dust Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
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
|
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||||
var settings = settings
|
var settings = settings
|
||||||
settings.dustEffect = value
|
settings.debugRatingLayout = value
|
||||||
return settings
|
return settings
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
@ -1506,7 +1506,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
|
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
|
||||||
#endif
|
#endif
|
||||||
entries.append(.alwaysDisplayTyping(experimentalSettings.alwaysDisplayTyping))
|
entries.append(.alwaysDisplayTyping(experimentalSettings.alwaysDisplayTyping))
|
||||||
entries.append(.dustEffect(experimentalSettings.dustEffect))
|
entries.append(.debugRatingLayout(experimentalSettings.debugRatingLayout))
|
||||||
}
|
}
|
||||||
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
|
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
|
||||||
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))
|
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))
|
||||||
|
|||||||
@ -532,7 +532,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
reactionActiveForeground: .clear,
|
reactionActiveForeground: .clear,
|
||||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
|
||||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||||
reactionStarsActiveForeground: .white,
|
reactionStarsActiveForeground: .white,
|
||||||
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
@ -548,7 +548,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
reactionActiveForeground: .clear,
|
reactionActiveForeground: .clear,
|
||||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
|
||||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||||
reactionStarsActiveForeground: .white,
|
reactionStarsActiveForeground: .white,
|
||||||
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
@ -570,7 +570,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
reactionActiveForeground: .clear,
|
reactionActiveForeground: .clear,
|
||||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
|
||||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||||
reactionStarsActiveForeground: .white,
|
reactionStarsActiveForeground: .white,
|
||||||
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
@ -586,7 +586,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
reactionActiveForeground: .clear,
|
reactionActiveForeground: .clear,
|
||||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
|
||||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||||
reactionStarsActiveForeground: .white,
|
reactionStarsActiveForeground: .white,
|
||||||
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
@ -605,7 +605,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
reactionActiveForeground: .clear,
|
reactionActiveForeground: .clear,
|
||||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
|
||||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||||
reactionStarsActiveForeground: .white,
|
reactionStarsActiveForeground: .white,
|
||||||
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
@ -621,7 +621,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
reactionActiveForeground: .clear,
|
reactionActiveForeground: .clear,
|
||||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
reactionStarsInactiveForeground: UIColor(rgb: 0xFFD738),
|
||||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||||
reactionStarsActiveForeground: .white,
|
reactionStarsActiveForeground: .white,
|
||||||
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
reactionInactiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1),
|
||||||
|
|||||||
@ -376,7 +376,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
|
extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
|
||||||
extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : 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,
|
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
|
||||||
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
|
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
|
||||||
|
isDark: arguments.presentationData.theme.theme.overallDarkAppearance
|
||||||
)
|
)
|
||||||
case .BubbleOutgoing, .ImageOutgoing, .FreeOutgoing:
|
case .BubbleOutgoing, .ImageOutgoing, .FreeOutgoing:
|
||||||
let themeColors = bubbleColorComponents(theme: arguments.presentationData.theme.theme, incoming: false, wallpaper: !arguments.presentationData.theme.wallpaper.isEmpty)
|
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,
|
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,
|
extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
|
||||||
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.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,
|
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
|
||||||
extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
|
extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
|
||||||
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
|
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
|
||||||
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
|
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
|
||||||
|
isDark: presentationData.theme.theme.overallDarkAppearance
|
||||||
)
|
)
|
||||||
case .outgoing:
|
case .outgoing:
|
||||||
themeColors = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty)
|
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,
|
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
|
||||||
extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
|
extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
|
||||||
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
|
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
|
||||||
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
|
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb,
|
||||||
|
isDark: presentationData.theme.theme.overallDarkAppearance
|
||||||
)
|
)
|
||||||
case .freeform:
|
case .freeform:
|
||||||
if presentationData.theme.wallpaper.isEmpty {
|
if presentationData.theme.wallpaper.isEmpty {
|
||||||
@ -128,7 +130,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
|
extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
|
||||||
extractedSelectedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
|
extractedSelectedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb,
|
||||||
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.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 MultilineTextComponent
|
||||||
import Svg
|
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 {
|
public final class PeerInfoRatingComponent: Component {
|
||||||
let backgroundColor: UIColor
|
let backgroundColor: UIColor
|
||||||
let borderColor: UIColor
|
let borderColor: UIColor
|
||||||
let foregroundColor: UIColor
|
let foregroundColor: UIColor
|
||||||
let level: Int
|
let level: Int
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
let debugLevel: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
backgroundColor: UIColor,
|
backgroundColor: UIColor,
|
||||||
borderColor: UIColor,
|
borderColor: UIColor,
|
||||||
foregroundColor: UIColor,
|
foregroundColor: UIColor,
|
||||||
level: Int,
|
level: Int,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void,
|
||||||
|
debugLevel: Bool = false
|
||||||
) {
|
) {
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.borderColor = borderColor
|
self.borderColor = borderColor
|
||||||
self.foregroundColor = foregroundColor
|
self.foregroundColor = foregroundColor
|
||||||
self.level = level
|
self.level = level
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.debugLevel = debugLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PeerInfoRatingComponent, rhs: PeerInfoRatingComponent) -> Bool {
|
public static func ==(lhs: PeerInfoRatingComponent, rhs: PeerInfoRatingComponent) -> Bool {
|
||||||
@ -39,6 +148,9 @@ public final class PeerInfoRatingComponent: Component {
|
|||||||
if lhs.level != rhs.level {
|
if lhs.level != rhs.level {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.debugLevel != rhs.debugLevel {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +168,7 @@ public final class PeerInfoRatingComponent: Component {
|
|||||||
private let borderLayer: SimpleLayer
|
private let borderLayer: SimpleLayer
|
||||||
private let backgroundLayer: SimpleLayer
|
private let backgroundLayer: SimpleLayer
|
||||||
|
|
||||||
//private var tempLevel: Int = 1
|
private var debugLevel: Int = 1
|
||||||
|
|
||||||
private var component: PeerInfoRatingComponent?
|
private var component: PeerInfoRatingComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
@ -78,18 +190,23 @@ public final class PeerInfoRatingComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
self.component?.action()
|
if component.debugLevel {
|
||||||
|
if self.debugLevel < 10 {
|
||||||
/*if self.tempLevel < 10 {
|
self.debugLevel += 1
|
||||||
self.tempLevel += 1
|
|
||||||
} else {
|
} else {
|
||||||
self.tempLevel += 10
|
self.debugLevel += 10
|
||||||
}
|
}
|
||||||
if self.tempLevel >= 110 {
|
if self.debugLevel >= 110 {
|
||||||
self.tempLevel = 1
|
self.debugLevel = 1
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
} else {
|
||||||
|
self.component?.action()
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: .immediate)*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,14 +219,51 @@ public final class PeerInfoRatingComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
let level = component.level
|
let level: Int
|
||||||
//let level = self.tempLevel
|
if component.debugLevel {
|
||||||
|
level = self.debugLevel
|
||||||
|
} else {
|
||||||
|
level = component.level
|
||||||
|
}
|
||||||
|
|
||||||
let iconSize = CGSize(width: 26.0, height: 26.0)
|
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: [
|
let attributedText = NSAttributedString(string: "\(level)", attributes: [
|
||||||
NSAttributedString.Key.font: Font.semibold(10.0),
|
NSAttributedString.Key.font: font,
|
||||||
NSAttributedString.Key.foregroundColor: component.foregroundColor
|
NSAttributedString.Key.foregroundColor: component.foregroundColor
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -157,13 +311,25 @@ public final class PeerInfoRatingComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let levelIndex: Int
|
let levelIndex: Int
|
||||||
if level <= 10 {
|
if level < 0 {
|
||||||
levelIndex = max(0, component.level)
|
levelIndex = 1
|
||||||
|
} else if level <= 10 {
|
||||||
|
levelIndex = max(1, level)
|
||||||
} else if level <= 90 {
|
} else if level <= 90 {
|
||||||
levelIndex = (level / 10) * 10
|
levelIndex = (level / 10) * 10
|
||||||
} else {
|
} else {
|
||||||
levelIndex = 90
|
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
|
let borderImage = generateImage(iconSize, rotatedContext: { size, context in
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
defer {
|
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 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) {
|
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 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) {
|
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 {
|
if let textLayout {
|
||||||
let titleScale: CGFloat
|
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
|
titleScale = 1.0
|
||||||
} else if level < 100 {
|
} else if level < 100 {
|
||||||
titleScale = 0.8
|
titleScale = 0.8
|
||||||
@ -216,19 +390,25 @@ public final class PeerInfoRatingComponent: Component {
|
|||||||
titleScale = 0.6
|
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)
|
let textFrame = CGRect(origin: CGPoint(x: (size.width - textLayout.size.width) * 0.5, y: (size.height - textLayout.size.height) * 0.5), size: textLayout.size)
|
||||||
if level == 1 {
|
|
||||||
textFrame.origin.x += UIScreenPixel
|
|
||||||
} else {
|
|
||||||
textFrame.origin.x += 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
context.saveGState()
|
context.saveGState()
|
||||||
context.translateBy(x: textFrame.midX, y: textFrame.midY)
|
context.translateBy(x: textFrame.midX, y: textFrame.midY)
|
||||||
context.scaleBy(x: titleScale, y: titleScale)
|
context.scaleBy(x: titleScale, y: titleScale)
|
||||||
context.translateBy(x: -textFrame.midX, y: -textFrame.midY)
|
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()
|
context.restoreGState()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -831,7 +831,36 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
headerButtonBackgroundColor = regularHeaderButtonBackgroundColor.mixedWith(collapsedHeaderButtonBackgroundColor, alpha: effectiveTransitionFraction)
|
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 _ = outerColor
|
||||||
let mainColor = UIColor(rgb: UInt32(bitPattern: innerColor))
|
let mainColor = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||||
|
|
||||||
@ -848,7 +877,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
ratingBackgroundColor = presentationData.theme.list.itemCheckColors.fillColor
|
ratingBackgroundColor = presentationData.theme.list.itemCheckColors.fillColor
|
||||||
ratingBorderColor = UIColor.clear
|
ratingBorderColor = UIColor.clear
|
||||||
ratingForegroundColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
ratingForegroundColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -1959,7 +1988,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
self.currentPendingStarRating = cachedData.pendingStarRating
|
self.currentPendingStarRating = cachedData.pendingStarRating
|
||||||
|
|
||||||
#if DEBUG
|
#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
|
#endif
|
||||||
} else {
|
} else {
|
||||||
self.currentStarRating = nil
|
self.currentStarRating = nil
|
||||||
@ -1996,7 +2028,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
pendingStarRating: self.currentPendingStarRating,
|
pendingStarRating: self.currentPendingStarRating,
|
||||||
customTheme: self.presentationData?.theme
|
customTheme: self.presentationData?.theme
|
||||||
))
|
))
|
||||||
}
|
},
|
||||||
|
debugLevel: self.context.sharedContext.immediateExperimentalUISettings.debugRatingLayout
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: width - 12.0 * 2.0, height: 100.0)
|
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 {
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
let _ = listSource.addToFolder(id: folderPreview.folder.id, items: [item])
|
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.initialStoryFolderId = nil
|
||||||
self.setStoryFolder(id: folder.id, assumeEmpty: false, animated: false)
|
self.setStoryFolder(id: folder.id, assumeEmpty: false, animated: false)
|
||||||
} else {
|
} 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
|
self.currentListState = state
|
||||||
|
|
||||||
var hasLocalItems = false
|
var hasLocalItems = false
|
||||||
@ -3923,6 +3937,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.presentRenameStoryFolder(id: folder.id, title: folder.title)
|
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
|
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 {
|
guard let self else {
|
||||||
@ -3937,7 +3952,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.shareFolder(id: folder.id)
|
self.shareFolder(id: folder.id)
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
}
|
|
||||||
|
|
||||||
if self.canManageStories {
|
if self.canManageStories {
|
||||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
|
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 {
|
if let listSource = self.listSource as? PeerStoryListContext {
|
||||||
let _ = listSource.addToFolder(id: folderId, items: items)
|
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
|
controller.navigationPresentation = .modal
|
||||||
@ -5392,6 +5416,64 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
collectibleItemInfo: nil
|
collectibleItemInfo: nil
|
||||||
)
|
)
|
||||||
self.parentController?.present(shareController, in: .window(.root))
|
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/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/PremiumUI",
|
"//submodules/PremiumUI",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||||
|
"//submodules/Components/HierarchyTrackingLayer",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -142,7 +142,7 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
|||||||
self.addSubview(self.dimView)
|
self.addSubview(self.dimView)
|
||||||
self.layer.addSublayer(self.backgroundLayer)
|
self.layer.addSublayer(self.backgroundLayer)
|
||||||
|
|
||||||
self.scrollView.delaysContentTouches = true
|
self.scrollView.delaysContentTouches = false
|
||||||
self.scrollView.canCancelContentTouches = true
|
self.scrollView.canCancelContentTouches = true
|
||||||
self.scrollView.clipsToBounds = false
|
self.scrollView.clipsToBounds = false
|
||||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
@ -283,6 +283,8 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
|||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isChangingPreview = transition.userData(TransitionHint.self)?.isChangingPreview ?? false
|
||||||
|
|
||||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.16)
|
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.16)
|
||||||
|
|
||||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
@ -354,13 +356,14 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
|||||||
let pendingPoints = pendingStarRating.rating.stars - component.starRating.stars
|
let pendingPoints = pendingStarRating.rating.stars - component.starRating.stars
|
||||||
|
|
||||||
if self.isPreviewingPendingRating {
|
if self.isPreviewingPendingRating {
|
||||||
|
//TODO:localize
|
||||||
secondaryDescriptionTextString = "This will be your rating in 21 days,\n after \(pendingPoints) points are added. [Back >]()"
|
secondaryDescriptionTextString = "This will be your rating in 21 days,\n after \(pendingPoints) points are added. [Back >]()"
|
||||||
} else {
|
} else {
|
||||||
let dayCount = (pendingStarRating.timestamp - timestamp) / (24 * 60 * 60)
|
let dayCount = (pendingStarRating.timestamp - timestamp) / (24 * 60 * 60)
|
||||||
if dayCount == 0 {
|
if dayCount == 0 {
|
||||||
secondaryDescriptionTextString = environment.strings.ProfileLevelInfo_MyDescriptionToday(Int32(pendingPoints))
|
secondaryDescriptionTextString = environment.strings.ProfileLevelInfo_MyDescriptionToday(Int32(pendingPoints))
|
||||||
} else {
|
} 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,
|
||||||
environment.theme.list.itemCheckColors.fillColor
|
environment.theme.list.itemCheckColors.fillColor
|
||||||
]
|
]
|
||||||
|
let _ = gradientColors
|
||||||
|
|
||||||
let levelFraction: CGFloat
|
var levelFraction: CGFloat
|
||||||
|
|
||||||
let badgeText: String
|
let badgeText: String
|
||||||
var badgeTextSuffix: String?
|
var badgeTextSuffix: String?
|
||||||
@ -413,8 +417,8 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
|||||||
if let nextLevelStars = pendingStarRating.rating.nextLevelStars {
|
if let nextLevelStars = pendingStarRating.rating.nextLevelStars {
|
||||||
badgeTextSuffix = " / \(starCountString(Int64(nextLevelStars), decimalSeparator: "."))"
|
badgeTextSuffix = " / \(starCountString(Int64(nextLevelStars), decimalSeparator: "."))"
|
||||||
}
|
}
|
||||||
if let nextLevelStars = pendingStarRating.rating.nextLevelStars {
|
if let nextLevelStars = pendingStarRating.rating.nextLevelStars, nextLevelStars > pendingStarRating.rating.stars {
|
||||||
levelFraction = Double(pendingStarRating.rating.stars) / Double(nextLevelStars)
|
levelFraction = Double(pendingStarRating.rating.stars - pendingStarRating.rating.currentLevelStars) / Double(nextLevelStars - pendingStarRating.rating.currentLevelStars)
|
||||||
} else {
|
} else {
|
||||||
levelFraction = 1.0
|
levelFraction = 1.0
|
||||||
}
|
}
|
||||||
@ -426,13 +430,17 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
|||||||
badgeTextSuffix = " / \(starCountString(Int64(nextLevelStars), decimalSeparator: "."))"
|
badgeTextSuffix = " / \(starCountString(Int64(nextLevelStars), decimalSeparator: "."))"
|
||||||
}
|
}
|
||||||
if let nextLevelStars = component.starRating.nextLevelStars {
|
if let nextLevelStars = component.starRating.nextLevelStars {
|
||||||
levelFraction = Double(component.starRating.stars) / Double(nextLevelStars)
|
levelFraction = Double(component.starRating.stars - component.starRating.currentLevelStars) / Double(nextLevelStars - component.starRating.currentLevelStars)
|
||||||
} else {
|
} else if component.starRating.stars > 0 {
|
||||||
levelFraction = 1.0
|
levelFraction = 1.0
|
||||||
|
} else {
|
||||||
|
levelFraction = 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let levelInfoSize = self.levelInfo.update(
|
levelFraction = max(0.0, levelFraction)
|
||||||
|
|
||||||
|
/*let levelInfoSize = self.levelInfo.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(PremiumLimitDisplayComponent(
|
component: AnyComponent(PremiumLimitDisplayComponent(
|
||||||
inactiveColor: environment.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
|
inactiveColor: environment.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
|
||||||
@ -453,18 +461,31 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
|||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 200.0)
|
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 let levelInfoView = self.levelInfo.view {
|
||||||
if levelInfoView.superview == nil {
|
if levelInfoView.superview == nil {
|
||||||
self.scrollContentView.addSubview(levelInfoView)
|
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
|
contentHeight += 129.0
|
||||||
|
|
||||||
let isChangingPreview = transition.userData(TransitionHint.self)?.isChangingPreview ?? false
|
|
||||||
|
|
||||||
if let secondaryDescriptionTextString {
|
if let secondaryDescriptionTextString {
|
||||||
if isChangingPreview, let secondaryDescriptionTextView = self.secondaryDescriptionText?.view {
|
if isChangingPreview, let secondaryDescriptionTextView = self.secondaryDescriptionText?.view {
|
||||||
self.secondaryDescriptionText = nil
|
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 switchToRecommendedChannels = false
|
||||||
var switchToGifts = false
|
var switchToGifts = false
|
||||||
var switchToGroupsInCommon = false
|
var switchToGroupsInCommon = false
|
||||||
|
var switchToStoryFolder: Int64?
|
||||||
switch mode {
|
switch mode {
|
||||||
case let .forumTopic(thread):
|
case let .forumTopic(thread):
|
||||||
forumTopicThread = thread
|
forumTopicThread = thread
|
||||||
@ -3950,10 +3951,12 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||||||
switchToGifts = true
|
switchToGifts = true
|
||||||
case .groupsInCommon:
|
case .groupsInCommon:
|
||||||
switchToGroupsInCommon = true
|
switchToGroupsInCommon = true
|
||||||
|
case let .storyAlbum(id):
|
||||||
|
switchToStoryFolder = id
|
||||||
default:
|
default:
|
||||||
break
|
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 {
|
} else if peer is TelegramUser {
|
||||||
var nearbyPeerDistance: Int32?
|
var nearbyPeerDistance: Int32?
|
||||||
var reactionSourceMessageId: MessageId?
|
var reactionSourceMessageId: MessageId?
|
||||||
|
|||||||
@ -68,6 +68,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var conferenceDebug: Bool
|
public var conferenceDebug: Bool
|
||||||
public var checkSerializedData: Bool
|
public var checkSerializedData: Bool
|
||||||
public var allForumsHaveTabs: Bool
|
public var allForumsHaveTabs: Bool
|
||||||
|
public var debugRatingLayout: Bool
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -113,7 +114,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
fakeAds: false,
|
fakeAds: false,
|
||||||
conferenceDebug: false,
|
conferenceDebug: false,
|
||||||
checkSerializedData: false,
|
checkSerializedData: false,
|
||||||
allForumsHaveTabs: false
|
allForumsHaveTabs: false,
|
||||||
|
debugRatingLayout: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +162,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
fakeAds: Bool,
|
fakeAds: Bool,
|
||||||
conferenceDebug: Bool,
|
conferenceDebug: Bool,
|
||||||
checkSerializedData: Bool,
|
checkSerializedData: Bool,
|
||||||
allForumsHaveTabs: Bool
|
allForumsHaveTabs: Bool,
|
||||||
|
debugRatingLayout: Bool
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -205,6 +208,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.conferenceDebug = conferenceDebug
|
self.conferenceDebug = conferenceDebug
|
||||||
self.checkSerializedData = checkSerializedData
|
self.checkSerializedData = checkSerializedData
|
||||||
self.allForumsHaveTabs = allForumsHaveTabs
|
self.allForumsHaveTabs = allForumsHaveTabs
|
||||||
|
self.debugRatingLayout = debugRatingLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
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.conferenceDebug = try container.decodeIfPresent(Bool.self, forKey: "conferenceDebug") ?? false
|
||||||
self.checkSerializedData = try container.decodeIfPresent(Bool.self, forKey: "checkSerializedData") ?? false
|
self.checkSerializedData = try container.decodeIfPresent(Bool.self, forKey: "checkSerializedData") ?? false
|
||||||
self.allForumsHaveTabs = try container.decodeIfPresent(Bool.self, forKey: "allForumsHaveTabs") ?? 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 {
|
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.conferenceDebug, forKey: "conferenceDebug")
|
||||||
try container.encodeIfPresent(self.checkSerializedData, forKey: "checkSerializedData")
|
try container.encodeIfPresent(self.checkSerializedData, forKey: "checkSerializedData")
|
||||||
try container.encodeIfPresent(self.allForumsHaveTabs, forKey: "allForumsHaveTabs")
|
try container.encodeIfPresent(self.allForumsHaveTabs, forKey: "allForumsHaveTabs")
|
||||||
|
try container.encodeIfPresent(self.debugRatingLayout, forKey: "debugRatingLayout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user