mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
f781dbd25f
commit
28d4e1e171
76 changed files with 4396 additions and 620 deletions
|
|
@ -15623,3 +15623,6 @@ Error: %8$@";
|
|||
"Chat.GiftPurchaseOffer.AcceptConfirmation.BadValue" = "The value of this gift is **%@** higher than the offer.";
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.Confirm" = "Confirm Sale";
|
||||
|
||||
|
||||
"Notification.PremiumGift.DaysTitle_1" = "%@ Day Premium";
|
||||
"Notification.PremiumGift.DaysTitle_any" = "%@ Days Premium";
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/MinimizedContainer",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import LegacyMessageInputPanelInputView
|
|||
import ReactionSelectionNode
|
||||
import TopMessageReactions
|
||||
import GlassBackgroundComponent
|
||||
import LiquidLens
|
||||
import TabSelectionRecognizer
|
||||
|
||||
private let legacyButtonSize = CGSize(width: 88.0, height: 49.0)
|
||||
private let glassButtonSize = CGSize(width: 72.0, height: 62.0)
|
||||
|
|
@ -911,13 +913,21 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private var backgroundView: GlassBackgroundView?
|
||||
private var liquidLensView: LiquidLensView?
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let scrollNode: ASScrollNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private let selectionNode: ASImageNode
|
||||
private var buttonViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
|
||||
private var itemsContainer = UIView()
|
||||
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var selectedItemsContainer = UIView()
|
||||
private var selectedItemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var itemSizes: [AnyHashable: CGSize] = [:]
|
||||
|
||||
private var tabSelectionRecognizer: TabSelectionRecognizer?
|
||||
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
|
||||
|
||||
private var textInputPanelNode: AttachmentTextInputPanelNode?
|
||||
private var progressNode: LoadingProgressNode?
|
||||
|
|
@ -987,7 +997,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
|
||||
self.mainButtonNode = MainButtonNode()
|
||||
self.secondaryButtonNode = MainButtonNode()
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
|
|
@ -995,7 +1005,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
switch style {
|
||||
case .glass:
|
||||
self.scrollNode.cornerRadius = glassButtonSize.height * 0.5
|
||||
self.scrollNode.addSubnode(self.selectionNode)
|
||||
//self.scrollNode.addSubnode(self.selectionNode)
|
||||
case .legacy:
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
self.containerNode.addSubnode(self.separatorNode)
|
||||
|
|
@ -1419,6 +1429,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.view.accessibilityTraits = .tabBar
|
||||
}
|
||||
|
||||
func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
if let layout = self.validLayout {
|
||||
let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func mainButtonPressed() {
|
||||
self.onMainButtonPressed()
|
||||
}
|
||||
|
|
@ -1473,6 +1489,66 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private func item(at point: CGPoint) -> AnyHashable? {
|
||||
var closestItem: (AnyHashable, CGFloat)?
|
||||
for (id, itemView) in self.itemViews {
|
||||
if itemView.frame.contains(point) {
|
||||
return id
|
||||
} else {
|
||||
let distance = abs(point.x - itemView.center.x)
|
||||
if let closestItemValue = closestItem {
|
||||
if closestItemValue.1 > distance {
|
||||
closestItem = (id, distance)
|
||||
}
|
||||
} else {
|
||||
closestItem = (id, distance)
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestItem?.0
|
||||
}
|
||||
|
||||
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
|
||||
guard let liquidLensView = self.liquidLensView else {
|
||||
return
|
||||
}
|
||||
let location = recognizer.location(in: liquidLensView.contentView)
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
|
||||
let startX = itemView.frame.minX - 4.0
|
||||
self.selectionGestureState = (startX, startX, itemId)
|
||||
|
||||
|
||||
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
case .changed:
|
||||
if var selectionGestureState = self.selectionGestureState {
|
||||
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self.view).x
|
||||
if let itemId = self.item(at: location) {
|
||||
selectionGestureState.itemId = itemId
|
||||
}
|
||||
self.selectionGestureState = selectionGestureState
|
||||
self.requestLayout(transition: .immediate)
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
self.selectionGestureState = nil
|
||||
if case .ended = recognizer.state {
|
||||
guard let index = self.buttons.firstIndex(where: { AnyHashable($0.key) == selectionGestureState.itemId }) else {
|
||||
return
|
||||
}
|
||||
if self.selectionChanged(self.buttons[index]) {
|
||||
self.selectedIndex = index
|
||||
}
|
||||
}
|
||||
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func updateViews(transition: ComponentTransition) {
|
||||
guard let layout = self.validLayout else {
|
||||
return
|
||||
|
|
@ -1533,13 +1609,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
|
||||
var buttonTransition = transition
|
||||
let buttonView: ComponentHostView<Empty>
|
||||
if let current = self.buttonViews[type.key] {
|
||||
let selectedButtonView: ComponentHostView<Empty>
|
||||
if let current = self.itemViews[type.key], let currentSelected = self.selectedItemViews[type.key] {
|
||||
buttonView = current
|
||||
selectedButtonView = currentSelected
|
||||
} else {
|
||||
buttonTransition = .immediate
|
||||
buttonView = ComponentHostView<Empty>()
|
||||
self.buttonViews[type.key] = buttonView
|
||||
self.scrollNode.view.addSubview(buttonView)
|
||||
self.itemViews[type.key] = buttonView
|
||||
self.itemsContainer.addSubview(buttonView)
|
||||
|
||||
selectedButtonView = ComponentHostView<Empty>()
|
||||
self.selectedItemViews[type.key] = selectedButtonView
|
||||
self.selectedItemsContainer.addSubview(selectedButtonView)
|
||||
}
|
||||
|
||||
if case let .app(bot) = type {
|
||||
|
|
@ -1578,7 +1660,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
style: self.panelStyle == .glass ? .glass : .legacy,
|
||||
type: type,
|
||||
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
|
||||
isSelected: i == self.selectedIndex,
|
||||
isSelected: false,
|
||||
strings: self.presentationData.strings,
|
||||
theme: self.presentationData.theme,
|
||||
action: { [weak self] in
|
||||
|
|
@ -1587,7 +1669,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
strongSelf.selectedIndex = i
|
||||
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
||||
|
||||
if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[type.key] {
|
||||
if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -1601,11 +1683,34 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
|
||||
)
|
||||
self.itemSizes[type.key] = actualButtonSize
|
||||
|
||||
let _ = selectedButtonView.update(
|
||||
transition: buttonTransition,
|
||||
component: AnyComponent(AttachButtonComponent(
|
||||
context: self.context,
|
||||
style: self.panelStyle == .glass ? .glass : .legacy,
|
||||
type: type,
|
||||
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
|
||||
isSelected: true,
|
||||
strings: self.presentationData.strings,
|
||||
theme: self.presentationData.theme,
|
||||
action: {
|
||||
}, longPressAction: { [weak self] in
|
||||
if let strongSelf = self, i == strongSelf.selectedIndex {
|
||||
strongSelf.longPressed(type)
|
||||
}
|
||||
})
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
|
||||
)
|
||||
|
||||
if i == self.selectedIndex {
|
||||
selectionFrame = CGRect(origin: CGPoint(x: buttonFrame.midX - actualButtonSize.width * 0.5, y: buttonFrame.minY), size: actualButtonSize)
|
||||
}
|
||||
buttonTransition.setFrame(view: buttonView, frame: buttonFrame)
|
||||
buttonTransition.setFrame(view: selectedButtonView, frame: buttonFrame)
|
||||
var accessibilityTitle = ""
|
||||
switch type {
|
||||
case .gallery:
|
||||
|
|
@ -1634,14 +1739,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
buttonView.accessibilityTraits = [.button]
|
||||
}
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.buttonViews {
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
itemView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.buttonViews.removeValue(forKey: id)
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0)
|
||||
|
|
@ -1916,29 +2021,51 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
textPanelWidth = layout.size.width - panelSideInset * 2.0
|
||||
}
|
||||
|
||||
var selectionFrame = CGRect()
|
||||
if self.selectedIndex >= 0 && self.selectedIndex < self.buttons.count, let itemView = self.itemViews[self.buttons[self.selectedIndex].key], let itemSize = self.itemSizes[self.buttons[self.selectedIndex].key] {
|
||||
selectionFrame = CGRect(origin: CGPoint(x: itemView.center.x - itemSize.width / 2.0, y: itemView.frame.minY), size: itemSize).insetBy(dx: -3.0, dy: 0.0)
|
||||
}
|
||||
|
||||
let lensSelection: (x: CGFloat, width: CGFloat)
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
lensSelection = (selectionGestureState.currentX, selectionFrame.width)
|
||||
} else {
|
||||
lensSelection = (selectionFrame.minX, selectionFrame.width)
|
||||
}
|
||||
|
||||
let glassPanelHeight: CGFloat = 62.0
|
||||
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom))
|
||||
if case .glass = self.panelStyle {
|
||||
let backgroundView: GlassBackgroundView
|
||||
if let current = self.backgroundView {
|
||||
let backgroundView: LiquidLensView
|
||||
if let current = self.liquidLensView {
|
||||
backgroundView = current
|
||||
} else {
|
||||
backgroundView = GlassBackgroundView()
|
||||
backgroundView = LiquidLensView(useBackgroundContainer: false)
|
||||
self.containerNode.view.addSubview(backgroundView)
|
||||
self.containerNode.view.addSubview(self.scrollNode.view)
|
||||
self.backgroundView = backgroundView
|
||||
//self.containerNode.view.addSubview(self.scrollNode.view)
|
||||
self.liquidLensView = backgroundView
|
||||
|
||||
backgroundView.contentView.addSubview(self.itemsContainer)
|
||||
backgroundView.selectedContentView.addSubview(self.selectedItemsContainer)
|
||||
|
||||
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
|
||||
self.tabSelectionRecognizer = tabSelectionRecognizer
|
||||
backgroundView.addGestureRecognizer(tabSelectionRecognizer)
|
||||
}
|
||||
let panelSize = CGSize(width: isSelecting ? textPanelWidth : layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight)
|
||||
|
||||
let backgroundViewColor: UIColor
|
||||
if self.presentationData.theme.overallDarkAppearance {
|
||||
backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55)
|
||||
} else {
|
||||
backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75)
|
||||
}
|
||||
|
||||
// let backgroundViewColor: UIColor
|
||||
// if self.presentationData.theme.overallDarkAppearance {
|
||||
// backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55)
|
||||
// } else {
|
||||
// backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75)
|
||||
// }
|
||||
//
|
||||
let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0)
|
||||
backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition))
|
||||
//backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition))
|
||||
|
||||
backgroundView.update(size: panelSize, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height), isDark: self.presentationData.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isSelecting, transition: ComponentTransition(transition))
|
||||
|
||||
transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
|
||||
transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize))
|
||||
}
|
||||
|
|
@ -2002,7 +2129,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut)
|
||||
alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
|
||||
containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
|
||||
if let backgroundView = self.backgroundView {
|
||||
|
||||
alphaTransition.updateAlpha(layer: self.itemsContainer.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
|
||||
containerTransition.updateTransformScale(layer: self.itemsContainer.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
|
||||
|
||||
alphaTransition.updateAlpha(layer: self.selectedItemsContainer.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
|
||||
containerTransition.updateTransformScale(layer: self.selectedItemsContainer.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
|
||||
|
||||
if let backgroundView = self.liquidLensView {
|
||||
containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0)
|
||||
}
|
||||
|
||||
|
|
@ -2022,7 +2156,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0)
|
||||
|
||||
let componentTransition = ComponentTransition.easeInOut(duration: 0.25)
|
||||
componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
componentTransition.animateBlur(layer: self.itemsContainer.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
|
||||
let blurTransition = ComponentTransition.easeInOut(duration: 0.18)
|
||||
transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01)
|
||||
|
|
@ -2054,7 +2188,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0)
|
||||
|
||||
let componentTransition = ComponentTransition.easeInOut(duration: 0.25)
|
||||
componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
componentTransition.animateBlur(layer: self.itemsContainer.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
|
||||
let blurTransition = ComponentTransition.easeInOut(duration: 0.18)
|
||||
transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01))
|
||||
|
|
@ -2187,4 +2321,3 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.updateViews(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||
public let handleSpoilers: Bool
|
||||
public let manualVisibilityControl: Bool
|
||||
public let resetAnimationsOnVisibilityChange: Bool
|
||||
public let enableLooping: Bool
|
||||
public let displaysAsynchronously: Bool
|
||||
public let maxWidth: CGFloat?
|
||||
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
||||
|
|
@ -71,6 +72,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||
handleSpoilers: Bool = false,
|
||||
manualVisibilityControl: Bool = false,
|
||||
resetAnimationsOnVisibilityChange: Bool = false,
|
||||
enableLooping: Bool = true,
|
||||
displaysAsynchronously: Bool = true,
|
||||
maxWidth: CGFloat? = nil,
|
||||
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
||||
|
|
@ -99,6 +101,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||
self.handleSpoilers = handleSpoilers
|
||||
self.manualVisibilityControl = manualVisibilityControl
|
||||
self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange
|
||||
self.enableLooping = enableLooping
|
||||
self.displaysAsynchronously = displaysAsynchronously
|
||||
self.maxWidth = maxWidth
|
||||
self.tapAction = tapAction
|
||||
|
|
@ -142,6 +145,9 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||
if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange {
|
||||
return false
|
||||
}
|
||||
if lhs.enableLooping != rhs.enableLooping {
|
||||
return false
|
||||
}
|
||||
if lhs.displaysAsynchronously != rhs.displaysAsynchronously {
|
||||
return false
|
||||
}
|
||||
|
|
@ -250,6 +256,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||
self.textNode.tapAttributeAction = component.tapAction
|
||||
self.textNode.longTapAttributeAction = component.longTapAction
|
||||
self.textNode.spoilerColor = component.spoilerColor
|
||||
self.textNode.enableLooping = component.enableLooping
|
||||
|
||||
self.textNode.resetEmojiToFirstFrameAutomatically = component.resetAnimationsOnVisibilityChange
|
||||
|
||||
|
|
|
|||
|
|
@ -974,8 +974,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||
transition.setFrame(view: contentView.tintMaskContainer, frame: contentFrame)
|
||||
}
|
||||
|
||||
let _ = bottomPanelHeight
|
||||
if let contentViewWithBackground = contentView.view.componentView as? PagerContentViewWithBackground {
|
||||
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.width, height: availableSize.height)), topPanelHeight: topPanelHeight, bottomPanelHeight: bottomPanelHeight, externalTintMaskContainer: component.externalTintMaskContainer == nil ? nil : contentView.tintMaskContainer, transition: contentTransition)
|
||||
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.width, height: availableSize.height)), topPanelHeight: topPanelHeight, bottomPanelHeight: 0.0, externalTintMaskContainer: component.externalTintMaskContainer == nil ? nil : contentView.tintMaskContainer, transition: contentTransition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ public final class SheetComponentEnvironment: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public let sheetComponentTag = GenericComponentViewTag()
|
||||
public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: Component {
|
||||
public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment)
|
||||
|
||||
|
|
|
|||
|
|
@ -403,8 +403,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||
}
|
||||
|
||||
let fieldHeight: CGFloat = 52.0
|
||||
let fieldSpacing: CGFloat = 16.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
let fieldSpacing: CGFloat = 10.0
|
||||
let buttonHeight: CGFloat = 52.0
|
||||
let justCreatedCallSeparatorSpacing: CGFloat = 16.0
|
||||
let justCreatedCallTextSpacing: CGFloat = 45.0
|
||||
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||
}
|
||||
})
|
||||
case let .starsMore(theme, title):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandStars()
|
||||
})
|
||||
case let .starsInfo(_, text):
|
||||
|
|
@ -521,14 +521,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||
if case let .channel(channel) = peer, case .group = channel.info {
|
||||
isGroup = true
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
}, setPeerIdWithRevealedOptions: { lhs, rhs in
|
||||
arguments.setItemIdWithRevealedOptions(lhs, rhs)
|
||||
}, removePeer: { id in
|
||||
arguments.removeChannel(id)
|
||||
})
|
||||
case let .channelAdd(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .compactPeerList, color: .accent, editing: false, action: {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .compactPeerList, color: .accent, editing: false, action: {
|
||||
arguments.openChannelsSelection()
|
||||
})
|
||||
case let .channelsInfo(_, text):
|
||||
|
|
@ -582,7 +582,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||
arguments.openPremiumIntro()
|
||||
})
|
||||
case let .prizeDescription(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.showPrizeDescription = value
|
||||
|
|
@ -590,7 +590,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||
}
|
||||
})
|
||||
case let .prizeDescriptionText(_, placeholder, value, count):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, maxLength: 128, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, maxLength: 128, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.prizeDescription = value
|
||||
|
|
@ -616,7 +616,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever
|
||||
}
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
arguments.dismissInput()
|
||||
var focus = false
|
||||
arguments.updateState { state in
|
||||
|
|
@ -677,7 +677,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
|||
case let .timeInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .winners(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.showWinners = value
|
||||
|
|
|
|||
|
|
@ -1679,34 +1679,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.newPerksDisposable = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager),
|
||||
ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager),
|
||||
ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager),
|
||||
ApplicationSpecificNotice.dismissedBusinessChatbotsBadge(accountManager: context.sharedContext.accountManager)
|
||||
).startStrict(next: { [weak self] dismissedBusinessBadge, dismissedBusinessLinksBadge, dismissedBusinessIntroBadge, dismissedBusinessChatbotsBadge in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var newPerks: [String] = []
|
||||
if !dismissedBusinessBadge {
|
||||
newPerks.append(PremiumPerk.business.identifier)
|
||||
}
|
||||
if !dismissedBusinessLinksBadge {
|
||||
newPerks.append(PremiumPerk.businessLinks.identifier)
|
||||
}
|
||||
if !dismissedBusinessIntroBadge {
|
||||
newPerks.append(PremiumPerk.businessIntro.identifier)
|
||||
}
|
||||
if !dismissedBusinessChatbotsBadge {
|
||||
newPerks.append(PremiumPerk.businessChatBots.identifier)
|
||||
}
|
||||
self.newPerks = newPerks
|
||||
self.updated()
|
||||
})
|
||||
|
||||
|
||||
self.adsEnabledDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AdsEnabled(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] adsEnabled in
|
||||
guard let self else {
|
||||
|
|
@ -2219,7 +2192,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
demoSubject = .todo
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
default:
|
||||
demoSubject = .doubleLimits
|
||||
}
|
||||
|
|
@ -2418,7 +2390,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
}
|
||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessChatbotsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .businessIntro:
|
||||
let _ = (accountContext.sharedContext.makeBusinessIntroSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|
|
@ -2428,7 +2399,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
}
|
||||
push(accountContext.sharedContext.makeBusinessIntroSetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessIntroBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .businessLinks:
|
||||
let _ = (accountContext.sharedContext.makeBusinessLinksSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|
|
@ -2438,7 +2408,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
}
|
||||
push(accountContext.sharedContext.makeBusinessLinksSetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessLinksBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
|
@ -2457,13 +2426,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
demoSubject = .businessAwayMessage
|
||||
case .businessChatBots:
|
||||
demoSubject = .businessChatBots
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessChatbotsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .businessIntro:
|
||||
demoSubject = .businessIntro
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessIntroBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .businessLinks:
|
||||
demoSubject = .businessLinks
|
||||
let _ = ApplicationSpecificNotice.setDismissedBusinessLinksBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,43 +102,43 @@ private enum DeleteAccountOptionsEntry: ItemListNodeEntry, Equatable {
|
|||
let arguments = arguments as! DeleteAccountOptionsArguments
|
||||
switch self {
|
||||
case let .changePhoneNumber(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.changePhoneNumber()
|
||||
})
|
||||
case let .addAccount(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteAddAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteAddAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.addAccount()
|
||||
})
|
||||
case let .changePrivacy(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.security, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.security, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.setupPrivacy()
|
||||
})
|
||||
case let .setTwoStepAuth(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.setupTwoStepAuth()
|
||||
})
|
||||
case let .setPasscode(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.setPasscode()
|
||||
})
|
||||
case let .clearCache(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.dataAndStorage, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.dataAndStorage, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.clearCache()
|
||||
})
|
||||
case let .clearSyncedContacts(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.clearSyncedContacts()
|
||||
})
|
||||
case let .deleteChats(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.deleteChats()
|
||||
})
|
||||
case let .contactSupport(_, title, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.contactSupport()
|
||||
})
|
||||
case let .deleteAccount(_, title):
|
||||
return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.deleteAccount()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ public enum EmojiGameInfo: Codable, Equatable {
|
|||
}
|
||||
|
||||
public struct Info: Codable, Equatable {
|
||||
let gameHash: String
|
||||
let previousStake: Int64
|
||||
let currentStreak: Int32
|
||||
let parameters: [Int32]
|
||||
let playsLeft: Int32?
|
||||
public let gameHash: String
|
||||
public let previousStake: Int64
|
||||
public let currentStreak: Int32
|
||||
public let parameters: [Int32]
|
||||
public let playsLeft: Int32?
|
||||
}
|
||||
|
||||
case available(Info)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Postbox
|
|||
public final class TelegramMediaDice: Media, Equatable {
|
||||
public struct GameOutcome: Equatable {
|
||||
let seed: Data
|
||||
let tonAmount: Int64
|
||||
public let tonAmount: Int64
|
||||
}
|
||||
|
||||
public let emoji: String
|
||||
|
|
|
|||
|
|
@ -193,12 +193,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||
case incomingVideoMessagePlayOnceTip = 62
|
||||
case outgoingVideoMessagePlayOnceTip = 63
|
||||
case savedMessageTagLabelSuggestion = 65
|
||||
case dismissedBusinessBadge = 68
|
||||
case monetizationIntroDismissed = 70
|
||||
case businessBotMessageTooltip = 71
|
||||
case dismissedBusinessIntroBadge = 72
|
||||
case dismissedBusinessLinksBadge = 73
|
||||
case dismissedBusinessChatbotsBadge = 74
|
||||
case captionAboveMediaTooltip = 75
|
||||
case channelSendGiftTooltip = 76
|
||||
case starGiftWearTips = 77
|
||||
|
|
@ -519,10 +515,6 @@ private struct ApplicationSpecificNoticeKeys {
|
|||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key)
|
||||
}
|
||||
|
||||
static func dismissedBusinessBadge() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessBadge.key)
|
||||
}
|
||||
|
||||
static func dismissedBirthdayPremiumGiftTip(peerId: PeerId) -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedBirthdayPremiumGiftTipNamespace), key: noticeKey(peerId: peerId, key: 0))
|
||||
}
|
||||
|
|
@ -543,18 +535,6 @@ private struct ApplicationSpecificNoticeKeys {
|
|||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.businessBotMessageTooltip.key)
|
||||
}
|
||||
|
||||
static func dismissedBusinessIntroBadge() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessIntroBadge.key)
|
||||
}
|
||||
|
||||
static func dismissedBusinessLinksBadge() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessLinksBadge.key)
|
||||
}
|
||||
|
||||
static func dismissedBusinessChatbotsBadge() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessChatbotsBadge.key)
|
||||
}
|
||||
|
||||
static func captionAboveMediaTooltip() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key)
|
||||
}
|
||||
|
|
@ -2154,27 +2134,6 @@ public struct ApplicationSpecificNotice {
|
|||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func setDismissedBusinessBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessBadge(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func dismissedBusinessBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessBadge())
|
||||
|> map { view -> Bool in
|
||||
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
|
||||
public static func dismissedBirthdayPremiumGiftTip(accountManager: AccountManager<TelegramAccountManagerTypes>, peerId: PeerId) -> Signal<Int32?, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBirthdayPremiumGiftTip(peerId: peerId))
|
||||
|
|
@ -2286,69 +2245,6 @@ public struct ApplicationSpecificNotice {
|
|||
}
|
||||
}
|
||||
|
||||
public static func setDismissedBusinessLinksBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessLinksBadge(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func dismissedBusinessLinksBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessLinksBadge())
|
||||
|> map { view -> Bool in
|
||||
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
|
||||
public static func setDismissedBusinessIntroBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessIntroBadge(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func dismissedBusinessIntroBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessIntroBadge())
|
||||
|> map { view -> Bool in
|
||||
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
|
||||
public static func setDismissedBusinessChatbotsBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessChatbotsBadge(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func dismissedBusinessChatbotsBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessChatbotsBadge())
|
||||
|> map { view -> Bool in
|
||||
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
|
||||
public static func getCaptionAboveMediaTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||
return accountManager.transaction { transaction -> Int32 in
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isAccessibilityElement = true
|
||||
|
||||
self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 9.0, isShimmering: true)
|
||||
self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 52.0, cornerRadius: 26.0, isShimmering: true)
|
||||
|
||||
self.footerNode = ImmediateTextNode()
|
||||
self.footerNode.textAlignment = .center
|
||||
|
|
|
|||
|
|
@ -36,15 +36,17 @@ public final class PermissionController: ViewController {
|
|||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.splashScreen = splashScreen
|
||||
|
||||
let navigationBarPresentationData: NavigationBarPresentationData
|
||||
if splashScreen {
|
||||
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
|
||||
} else {
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
|
||||
}
|
||||
// let navigationBarPresentationData: NavigationBarPresentationData
|
||||
// if splashScreen {
|
||||
// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
|
||||
// } else {
|
||||
let navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)
|
||||
// }
|
||||
|
||||
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
||||
|
||||
self._hasGlassStyle = true
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.updateThemeAndStrings()
|
||||
|
|
@ -86,15 +88,15 @@ public final class PermissionController: ViewController {
|
|||
private func updateThemeAndStrings() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
let navigationBarPresentationData: NavigationBarPresentationData
|
||||
if self.splashScreen {
|
||||
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
|
||||
} else {
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
|
||||
}
|
||||
// let navigationBarPresentationData: NavigationBarPresentationData
|
||||
// if self.splashScreen {
|
||||
// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
|
||||
// } else {
|
||||
let navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)
|
||||
// }
|
||||
|
||||
self.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate)
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
|
||||
//self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
|
||||
if self.navigationItem.rightBarButtonItem != nil {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1764,6 +1764,17 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
resultTitleString = strings.Conversation_StoryExpiredMentionTextOutgoing(compactPeerName)
|
||||
}
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
} else if let dice = media as? TelegramMediaDice, let gameOutcome = dice.gameOutcome {
|
||||
if let value = dice.value, value > 1 {
|
||||
//TODO:localize
|
||||
let value = formatTonAmountText(gameOutcome.tonAmount, dateTimeFormat: dateTimeFormat)
|
||||
let attributedText = NSMutableAttributedString(string: "You won $\(value)", font: titleFont, textColor: primaryTextColor)
|
||||
if let range = attributedText.string.range(of: "$") {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: true)), range: NSRange(range, in: attributedText.string))
|
||||
attributedText.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedText.string))
|
||||
}
|
||||
attributedString = attributedText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -508,6 +508,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode",
|
||||
"//submodules/TelegramUI/Components/Settings/PasskeysScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftDemoScreen",
|
||||
"//submodules/TelegramUI/Components/EmojiGameStakeScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ swift_library(
|
|||
"//submodules/TelegramCallsUI",
|
||||
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
|
||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -2097,7 +2097,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||
if !isSticker, case .none = component.cameraState.recording, component.cameraState.isStreaming == .none && !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne {
|
||||
let availableModeControlSize: CGSize
|
||||
if isTablet {
|
||||
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
|
||||
availableModeControlSize = CGSize(width: floor(panelWidth), height: 120.0)
|
||||
} else {
|
||||
availableModeControlSize = availableSize
|
||||
}
|
||||
|
|
@ -2131,7 +2131,6 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||
modeControlPosition = CGPoint(x: availableSize.width / 2.0, y: availableSize.height - environment.safeInsets.bottom + modeControl.size.height / 2.0 + controlsBottomInset + 16.0)
|
||||
}
|
||||
context.add(modeControl
|
||||
.clipsToBounds(true)
|
||||
.position(modeControlPosition)
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import ComponentFlow
|
|||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
import GlassBackgroundComponent
|
||||
import LiquidLens
|
||||
import TabSelectionRecognizer
|
||||
|
||||
extension CameraMode {
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
|
|
@ -70,28 +72,17 @@ final class ModeComponent: Component {
|
|||
|
||||
final class View: UIView, ComponentTaggedView {
|
||||
private var component: ModeComponent?
|
||||
private var state: EmptyComponentState?
|
||||
|
||||
final class ItemView: HighlightTrackingButton {
|
||||
var pressed: () -> Void = {
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.isExclusiveTouch = true
|
||||
|
||||
self.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
func update(isTablet: Bool, value: String, selected: Bool, tintColor: UIColor) -> CGSize {
|
||||
let accentColor: UIColor
|
||||
let normalColor: UIColor
|
||||
|
|
@ -113,9 +104,15 @@ final class ModeComponent: Component {
|
|||
}
|
||||
|
||||
private var backgroundView = UIView()
|
||||
private var glassContainerView = GlassBackgroundContainerView()
|
||||
private var selectionView = GlassBackgroundView()
|
||||
private var itemViews: [Int32: ItemView] = [:]
|
||||
private var backgroundContainer = GlassBackgroundContainerView()
|
||||
|
||||
private let liquidLensView: LiquidLensView
|
||||
|
||||
private var itemViews: [AnyHashable: ItemView] = [:]
|
||||
private var selectedItemViews: [AnyHashable: ItemView] = [:]
|
||||
|
||||
private var tabSelectionRecognizer: TabSelectionRecognizer?
|
||||
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
|
||||
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let component = self.component, let componentTag = component.tag {
|
||||
|
|
@ -128,16 +125,23 @@ final class ModeComponent: Component {
|
|||
}
|
||||
|
||||
init() {
|
||||
self.liquidLensView = LiquidLensView(useBackgroundContainer: false)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11)
|
||||
self.backgroundView.layer.cornerRadius = 24.0
|
||||
|
||||
|
||||
self.layer.allowsGroupOpacity = true
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.backgroundView.addSubview(self.glassContainerView)
|
||||
self.glassContainerView.contentView.addSubview(self.selectionView)
|
||||
self.backgroundView.addSubview(self.backgroundContainer)
|
||||
|
||||
self.backgroundContainer.contentView.addSubview(self.liquidLensView)
|
||||
|
||||
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
|
||||
self.tabSelectionRecognizer = tabSelectionRecognizer
|
||||
self.liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
|
@ -162,14 +166,69 @@ final class ModeComponent: Component {
|
|||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
return self.backgroundView.frame.contains(point)
|
||||
}
|
||||
|
||||
func update(component: ModeComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let isTablet = component.isTablet
|
||||
let updatedMode = component.updatedMode
|
||||
private func item(at point: CGPoint) -> AnyHashable? {
|
||||
var closestItem: (AnyHashable, CGFloat)?
|
||||
for (id, itemView) in self.itemViews {
|
||||
if itemView.frame.contains(point) {
|
||||
return id
|
||||
} else {
|
||||
let distance = abs(point.x - itemView.center.x)
|
||||
if let closestItemValue = closestItem {
|
||||
if closestItemValue.1 > distance {
|
||||
closestItem = (id, distance)
|
||||
}
|
||||
} else {
|
||||
closestItem = (id, distance)
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestItem?.0
|
||||
}
|
||||
|
||||
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
let location = recognizer.location(in: self.liquidLensView.contentView)
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
|
||||
let startX = itemView.frame.minX - 4.0
|
||||
self.selectionGestureState = (startX, startX, itemId)
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
}
|
||||
case .changed:
|
||||
if var selectionGestureState = self.selectionGestureState {
|
||||
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
|
||||
if let itemId = self.item(at: location) {
|
||||
selectionGestureState.itemId = itemId
|
||||
}
|
||||
self.selectionGestureState = selectionGestureState
|
||||
self.state?.updated(transition: .immediate, isLocal: true)
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
self.selectionGestureState = nil
|
||||
if case .ended = recognizer.state {
|
||||
guard let item = component.availableModes.first(where: { AnyHashable($0.rawValue) == selectionGestureState.itemId }) else {
|
||||
return
|
||||
}
|
||||
component.updatedMode(item)
|
||||
}
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: ModeComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let isTablet = component.isTablet
|
||||
|
||||
self.glassContainerView.isHidden = component.isTablet
|
||||
self.backgroundView.backgroundColor = component.isTablet ? .clear : UIColor(rgb: 0xffffff, alpha: 0.11)
|
||||
|
||||
let inset: CGFloat = 23.0
|
||||
|
|
@ -180,25 +239,34 @@ final class ModeComponent: Component {
|
|||
var selectedCenter = itemFrame.minX
|
||||
var selectedFrame = itemFrame
|
||||
|
||||
var validKeys: Set<Int32> = Set()
|
||||
var validKeys: Set<AnyHashable> = Set()
|
||||
for mode in component.availableModes.reversed() {
|
||||
let id = mode.rawValue
|
||||
validKeys.insert(id)
|
||||
|
||||
let itemView: ItemView
|
||||
if let current = self.itemViews[id] {
|
||||
let selectedItemView: ItemView
|
||||
if let current = self.itemViews[id], let currentSelected = self.selectedItemViews[id] {
|
||||
itemView = current
|
||||
selectedItemView = currentSelected
|
||||
} else {
|
||||
itemView = ItemView()
|
||||
self.backgroundView.addSubview(itemView)
|
||||
itemView.isUserInteractionEnabled = false
|
||||
self.itemViews[id] = itemView
|
||||
}
|
||||
itemView.pressed = {
|
||||
updatedMode(mode)
|
||||
self.liquidLensView.contentView.addSubview(itemView)
|
||||
|
||||
selectedItemView = ItemView()
|
||||
selectedItemView.isUserInteractionEnabled = false
|
||||
self.selectedItemViews[id] = selectedItemView
|
||||
self.liquidLensView.selectedContentView.addSubview(selectedItemView)
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: mode == component.currentMode, tintColor: component.tintColor)
|
||||
let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: false, tintColor: component.tintColor)
|
||||
itemView.bounds = CGRect(origin: .zero, size: itemSize)
|
||||
|
||||
let _ = selectedItemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: true, tintColor: component.tintColor)
|
||||
selectedItemView.bounds = CGRect(origin: .zero, size: itemSize)
|
||||
|
||||
itemFrame = CGRect(origin: itemFrame.origin, size: itemSize)
|
||||
|
||||
if mode == component.currentMode {
|
||||
|
|
@ -207,12 +275,14 @@ final class ModeComponent: Component {
|
|||
|
||||
if isTablet {
|
||||
itemView.center = CGPoint(x: availableSize.width / 2.0, y: itemFrame.midY)
|
||||
selectedItemView.center = itemView.center
|
||||
if mode == component.currentMode {
|
||||
selectedCenter = itemFrame.midY
|
||||
}
|
||||
itemFrame = itemFrame.offsetBy(dx: 0.0, dy: tabletButtonSize.height + spacing)
|
||||
} else {
|
||||
itemView.center = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
selectedItemView.center = itemView.center
|
||||
if mode == component.currentMode {
|
||||
selectedCenter = itemFrame.midX
|
||||
}
|
||||
|
|
@ -221,7 +291,7 @@ final class ModeComponent: Component {
|
|||
i += 1
|
||||
}
|
||||
|
||||
var removeKeys: [Int32] = []
|
||||
var removeKeys: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validKeys.contains(id) {
|
||||
removeKeys.append(id)
|
||||
|
|
@ -248,12 +318,19 @@ final class ModeComponent: Component {
|
|||
}
|
||||
|
||||
let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size)
|
||||
transition.setFrame(view: self.glassContainerView, frame: containerFrame)
|
||||
transition.setFrame(view: self.backgroundContainer, frame: containerFrame)
|
||||
|
||||
let selectionFrame = selectedFrame.insetBy(dx: -20.0, dy: 3.0)
|
||||
self.glassContainerView.update(size: containerFrame.size, isDark: true, transition: .immediate)
|
||||
self.selectionView.update(size: selectionFrame.size, cornerRadius: selectionFrame.height * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.16)), transition: transition)
|
||||
transition.setFrame(view: self.selectionView, frame: selectionFrame)
|
||||
let selectionFrame = selectedFrame.insetBy(dx: -23.0, dy: 3.0)
|
||||
let lensSelection: (x: CGFloat, width: CGFloat)
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
lensSelection = (selectionGestureState.currentX, selectionFrame.width)
|
||||
} else {
|
||||
lensSelection = (selectionFrame.minX, selectionFrame.width)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size))
|
||||
self.liquidLensView.update(size: containerFrame.size, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: selectionFrame.height), isDark: true, isLifted: self.selectionGestureState != nil, isCollapsed: false, transition: transition)
|
||||
self.backgroundContainer.update(size: containerFrame.size, isDark: true, transition: .immediate)
|
||||
|
||||
return size
|
||||
}
|
||||
|
|
@ -264,7 +341,7 @@ final class ModeComponent: Component {
|
|||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -440,6 +440,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||
if node.iconNode == nil {
|
||||
let iconNode = ASImageNode()
|
||||
iconNode.contentMode = .center
|
||||
iconNode.isUserInteractionEnabled = false
|
||||
node.iconNode = iconNode
|
||||
node.addSubnode(iconNode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ swift_library(
|
|||
"//submodules/WallpaperBackgroundNode",
|
||||
"//submodules/LocalMediaResources",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/ChatPresentationInterfaceState",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import ManagedDiceAnimationNode
|
|||
import MessageHaptics
|
||||
import ChatMessageTransitionNode
|
||||
import ChatMessageSuggestedPostInfoNode
|
||||
import TelegramStringFormatting
|
||||
|
||||
private let nameFont = Font.medium(14.0)
|
||||
private let inlineBotPrefixFont = Font.regular(14.0)
|
||||
|
|
@ -99,6 +100,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||
private var swipeToReplyFeedback: HapticFeedback?
|
||||
|
||||
private let labelNode: TextNodeWithEntities
|
||||
private var labelBackgroundNode: WallpaperBubbleBackgroundNode?
|
||||
private let labelBackgroundMaskNode: ASImageNode
|
||||
private var cachedMaskLabelBackgroundImage: (CGPoint, UIImage, [CGRect])?
|
||||
|
||||
private var selectionNode: ChatMessageSelectionNode?
|
||||
private var deliveryFailedNode: ChatMessageDeliveryFailedNode?
|
||||
private var shareButtonNode: ChatMessageShareButton?
|
||||
|
|
@ -161,6 +167,12 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
self.textNode.textNode.displaysAsynchronously = false
|
||||
self.textNode.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.labelNode = TextNodeWithEntities()
|
||||
self.labelNode.textNode.isUserInteractionEnabled = false
|
||||
self.labelNode.textNode.displaysAsynchronously = false
|
||||
|
||||
self.labelBackgroundMaskNode = ASImageNode()
|
||||
|
||||
super.init(rotated: rotated)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
|
|
@ -469,9 +481,22 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
}
|
||||
} else if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? ManagedDiceAnimationNode {
|
||||
if let value = telegramDice.value {
|
||||
let wasRolling = diceNode.isRolling
|
||||
diceNode.setState(value == 0 ? .rolling : .value(value, true))
|
||||
|
||||
if wasRolling && !diceNode.isRolling {
|
||||
Queue.mainQueue().after(3.0, {
|
||||
self.labelNode.textNode.alpha = 1.0
|
||||
self.labelBackgroundNode?.alpha = 1.0
|
||||
self.labelNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
self.labelBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
diceNode.setState(.rolling)
|
||||
|
||||
self.labelNode.textNode.alpha = 0.0
|
||||
self.labelBackgroundNode?.alpha = 0.0
|
||||
}
|
||||
} else if self.telegramFile == nil && self.telegramDice == nil {
|
||||
let (emoji, fitz) = item.message.text.basicEmoji
|
||||
|
|
@ -816,6 +841,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
|
||||
let reactionButtonsLayout = ChatMessageReactionButtonsNode.asyncLayout(self.reactionButtonsNode)
|
||||
|
||||
let makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode)
|
||||
let cachedMaskLabelBackgroundImage = self.cachedMaskLabelBackgroundImage
|
||||
|
||||
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
|
||||
|
||||
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
|
||||
|
|
@ -850,6 +878,44 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
let avatarInset: CGFloat
|
||||
var hasAvatar = false
|
||||
|
||||
let labelAttributedText = universalServiceMessageString(presentationData: (item.presentationData.theme.theme, item.presentationData.theme.wallpaper), strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true)
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var labelRects = labelLayout.linesRects()
|
||||
if labelRects.count > 1 {
|
||||
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
|
||||
for i in 0 ..< sortedIndices.count {
|
||||
let index = sortedIndices[i]
|
||||
for j in -1 ... 1 {
|
||||
if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
|
||||
if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 {
|
||||
labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
|
||||
labelRects[index].size.width = labelRects[index + j].size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0 ..< labelRects.count {
|
||||
labelRects[i] = labelRects[i].insetBy(dx: -7.0, dy: floor((labelRects[i].height - 22.0) / 2.0))
|
||||
labelRects[i].size.height = 22.0
|
||||
labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0)
|
||||
}
|
||||
|
||||
let backgroundMaskImage: (CGPoint, UIImage)?
|
||||
var backgroundMaskUpdated = false
|
||||
if labelLayout.size.height > 0.0 {
|
||||
if let (currentOffset, currentImage, currentRects) = cachedMaskLabelBackgroundImage, currentRects == labelRects {
|
||||
backgroundMaskImage = (currentOffset, currentImage)
|
||||
} else {
|
||||
backgroundMaskImage = LinkHighlightingNode.generateImage(color: .black, inset: 0.0, innerRadius: 11.0, outerRadius: 11.0, rects: labelRects, useModernPathCalculation: false)
|
||||
backgroundMaskUpdated = true
|
||||
}
|
||||
} else {
|
||||
backgroundMaskImage = nil
|
||||
}
|
||||
|
||||
switch item.chatLocation {
|
||||
case let .peer(peerId):
|
||||
if peerId != item.context.account.peerId {
|
||||
|
|
@ -1493,6 +1559,51 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
let _ = labelApply(TextNodeWithEntities.Arguments(
|
||||
context: item.context,
|
||||
cache: item.controllerInteraction.presentationContext.animationCache,
|
||||
renderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||
placeholderColor: item.presentationData.theme.theme.chat.message.freeform.withWallpaper.reactionInactiveBackground,
|
||||
attemptSynchronous: synchronousLoads
|
||||
))
|
||||
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size)
|
||||
strongSelf.labelNode.textNode.frame = labelFrame
|
||||
if strongSelf.labelNode.textNode.supernode == nil, labelLayout.size.height > 0.0 {
|
||||
strongSelf.addSubnode(strongSelf.labelNode.textNode)
|
||||
}
|
||||
|
||||
let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
|
||||
if let (offset, image) = backgroundMaskImage {
|
||||
if strongSelf.labelBackgroundNode == nil {
|
||||
if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
backgroundNode.alpha = strongSelf.labelNode.textNode.alpha
|
||||
strongSelf.labelBackgroundNode = backgroundNode
|
||||
strongSelf.insertSubnode(backgroundNode, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
if backgroundMaskUpdated, let backgroundNode = strongSelf.labelBackgroundNode {
|
||||
if labelRects.count == 1 {
|
||||
backgroundNode.clipsToBounds = true
|
||||
backgroundNode.cornerRadius = labelRects[0].height / 2.0
|
||||
backgroundNode.view.mask = nil
|
||||
} else {
|
||||
backgroundNode.clipsToBounds = false
|
||||
backgroundNode.cornerRadius = 0.0
|
||||
backgroundNode.view.mask = strongSelf.labelBackgroundMaskNode.view
|
||||
}
|
||||
}
|
||||
|
||||
if let backgroundNode = strongSelf.labelBackgroundNode {
|
||||
backgroundNode.layer.frame = CGRect(origin: CGPoint(x: baseBackgroundFrame.minX + offset.x, y: baseBackgroundFrame.minY + offset.y), size: image.size)
|
||||
}
|
||||
strongSelf.labelBackgroundMaskNode.image = image
|
||||
strongSelf.labelBackgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size)
|
||||
|
||||
strongSelf.cachedMaskLabelBackgroundImage = (offset, image, labelRects)
|
||||
}
|
||||
|
||||
var updatedImageFrame: CGRect
|
||||
var contextContentFrame: CGRect
|
||||
if let _ = emojiString {
|
||||
|
|
|
|||
|
|
@ -455,7 +455,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
switch action.action {
|
||||
case let .giftPremium(_, _, daysValue, _, _, giftText, giftEntities):
|
||||
months = max(3, Int32(round(Float(daysValue) / 30.0)))
|
||||
if months == 12 {
|
||||
if daysValue < 30 {
|
||||
title = item.presentationData.strings.Notification_PremiumGift_DaysTitle(daysValue)
|
||||
} else if months == 12 {
|
||||
title = item.presentationData.strings.Notification_PremiumGift_YearsTitle(1)
|
||||
} else {
|
||||
title = item.presentationData.strings.Notification_PremiumGift_MonthsTitle(months)
|
||||
|
|
@ -513,7 +515,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
title = item.presentationData.strings.Notification_StarsGiveaway_Title
|
||||
let starsString = item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(clamping: count)).replacingOccurrences(of: " ", with: "\u{00A0}")
|
||||
text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, starsString).string
|
||||
case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _, giftText, giftEntities):
|
||||
case let .giftCode(_, fromGiveaway, unclaimed, channelId, daysValue, _, _, _, _, giftText, giftEntities):
|
||||
let monthsValue = max(3, Int32(round(Float(daysValue) / 30.0)))
|
||||
if channelId == nil {
|
||||
months = monthsValue
|
||||
if months == 12 {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ public struct InteractiveEmojiConfiguration {
|
|||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration {
|
||||
if let data = appConfiguration.data, let emojis = data["emojies_send_dice"] as? [String] {
|
||||
if let data = appConfiguration.data, var emojis = data["emojies_send_dice"] as? [String] {
|
||||
var successParameters: [String: InteractiveEmojiSuccessParameters] = [:]
|
||||
if let success = data["emojies_send_dice_success"] as? [String: [String: Double]] {
|
||||
for (key, dict) in success {
|
||||
|
|
@ -109,6 +109,11 @@ public struct InteractiveEmojiConfiguration {
|
|||
}
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
if !emojis.contains("🎲") {
|
||||
emojis.append("🎲")
|
||||
}
|
||||
#endif
|
||||
return InteractiveEmojiConfiguration(emojis: emojis, successParameters: successParameters)
|
||||
} else {
|
||||
return .defaultValue
|
||||
|
|
@ -126,6 +131,10 @@ public final class ManagedDiceAnimationNode: ManagedAnimationNode {
|
|||
private let configuration = Promise<InteractiveEmojiConfiguration?>()
|
||||
private let emojis = Promise<[TelegramMediaFile]>()
|
||||
|
||||
public var isRolling: Bool {
|
||||
return self.diceState == .rolling
|
||||
}
|
||||
|
||||
public var success: (() -> Void)?
|
||||
|
||||
public init(context: AccountContext, emoji: String) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import LegacyMessageInputPanelInputView
|
|||
import AttachmentTextInputPanelNode
|
||||
import GlassBackgroundComponent
|
||||
|
||||
private let keyboardCornerRadius: CGFloat = 30.0
|
||||
|
||||
public final class EmptyInputView: UIView, UIInputViewAudioFeedback {
|
||||
public var enableInputClicksWhenVisible: Bool {
|
||||
return true
|
||||
|
|
@ -490,7 +492,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
|
||||
self.clippingView = UIView()
|
||||
self.clippingView.clipsToBounds = true
|
||||
self.clippingView.layer.cornerRadius = 20.0
|
||||
self.clippingView.layer.cornerRadius = keyboardCornerRadius
|
||||
|
||||
self.entityKeyboardView = ComponentHostView<Empty>()
|
||||
|
||||
|
|
@ -1904,8 +1906,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
isContentInFocus: isVisible,
|
||||
containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -34.0 : 0.0, left: leftInset, bottom: keyboardBottomInset, right: rightInset),
|
||||
topPanelInsets: UIEdgeInsets(),
|
||||
containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -42.0 : 0.0, left: leftInset, bottom: keyboardBottomInset, right: rightInset),
|
||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0),
|
||||
emojiContent: emojiContent,
|
||||
stickerContent: stickerContent,
|
||||
maskContent: nil,
|
||||
|
|
@ -2030,16 +2032,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
backgroundFrame.size.height += 32.0
|
||||
|
||||
if backgroundChromeView.image == nil {
|
||||
backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 20.0 * 2.0, height: 20.0 * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear)
|
||||
backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: keyboardCornerRadius * 2.0, height: keyboardCornerRadius * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear)
|
||||
}
|
||||
if backgroundTintView.image == nil {
|
||||
backgroundTintView.image = generateStretchableFilledCircleImage(diameter: 20.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
backgroundTintView.image = generateStretchableFilledCircleImage(diameter: keyboardCornerRadius * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
backgroundTintView.tintColor = interfaceState.theme.chat.inputMediaPanel.backgroundColor
|
||||
|
||||
transition.updateFrame(view: backgroundView, frame: backgroundFrame)
|
||||
backgroundView.updateColor(color: .clear, forceKeepBlur: true, transition: .immediate)
|
||||
backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition)
|
||||
backgroundView.update(size: backgroundFrame.size, cornerRadius: keyboardCornerRadius, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition)
|
||||
|
||||
transition.updateFrame(view: backgroundChromeView, frame: backgroundFrame.insetBy(dx: -1.0, dy: 0.0))
|
||||
|
||||
|
|
|
|||
55
submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD
Normal file
55
submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "EmojiGameStakeScreen",
|
||||
module_name = "EmojiGameStakeScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SheetComponent",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramUI/Components/ScrollComponent",
|
||||
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/PasswordSetupUI",
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
||||
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/Stars/BalanceNeededScreen",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponentResourceContent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -53,6 +53,9 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -419,13 +419,13 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
|
|||
|
||||
context.setFillColor(color.withMultipliedAlpha(0.2).cgColor)
|
||||
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 21.0).cgPath)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 30.0).cgPath)
|
||||
context.fillPath()
|
||||
context.setFillColor(color.cgColor)
|
||||
|
||||
let plusSize = CGSize(width: 3.5, height: 28.0)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height).offsetBy(dx: 0.0, dy: -17.0), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width).offsetBy(dx: 0.0, dy: -17.0), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||
let plusSize = CGSize(width: 4.0, height: 27.0)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height).offsetBy(dx: 0.0, dy: -18.0), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width).offsetBy(dx: 0.0, dy: -18.0), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||
context.fillPath()
|
||||
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
|
@ -437,7 +437,7 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
|
|||
let components = string.components(separatedBy: "\n")
|
||||
for component in components {
|
||||
context.saveGState()
|
||||
let attributedString = NSAttributedString(string: component, attributes: [NSAttributedString.Key.font: Font.medium(17.0), NSAttributedString.Key.foregroundColor: color])
|
||||
let attributedString = NSAttributedString(string: component, attributes: [NSAttributedString.Key.font: Font.medium(16.0), NSAttributedString.Key.foregroundColor: color])
|
||||
|
||||
let line = CTLineCreateWithAttributedString(attributedString)
|
||||
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||
|
|
|
|||
|
|
@ -4026,7 +4026,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||
private func updateTopPanelSeparator(transition: ComponentTransition) {
|
||||
if let topPanelSeparator = self.topPanelSeparator {
|
||||
var offset = self.scrollView.contentOffset.y
|
||||
let startOffset: CGFloat = 40.0 - self.topPanelHeight
|
||||
let startOffset: CGFloat = 46.0 - self.topPanelHeight
|
||||
let endOffset: CGFloat = startOffset + 10.0
|
||||
|
||||
offset = min(max(offset, startOffset), endOffset)
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
if let _ = component.maskContent?.inputInteractionHolder.inputInteraction?.openStickerSettings {
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
maskContent.inputInteractionHolder.inputInteraction?.openStickerSettings?()
|
||||
}
|
||||
|
|
@ -417,7 +417,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage {
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Media Editor/AddImage",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
addImage()
|
||||
}
|
||||
|
|
@ -537,7 +537,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
if let _ = component.stickerContent?.inputInteractionHolder.inputInteraction?.openStickerSettings {
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
stickerContent.inputInteractionHolder.inputInteraction?.openStickerSettings?()
|
||||
}
|
||||
|
|
@ -546,7 +546,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage {
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Media Editor/AddImage",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
addImage()
|
||||
}
|
||||
|
|
@ -649,7 +649,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
if let _ = deleteBackwards {
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Chat/Input/Media/EntityInputGlobeIcon",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self, let component = strongSelf.component else {
|
||||
return
|
||||
|
|
@ -660,7 +660,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
} else if let addImage = component.emojiContent?.inputInteractionHolder.inputInteraction?.addImage {
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Media Editor/AddImage",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
addImage()
|
||||
}
|
||||
|
|
@ -671,7 +671,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
if let _ = deleteBackwards {
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton(
|
||||
icon: "Chat/Input/Media/EntityInputClearIcon",
|
||||
color: component.theme.chat.inputPanel.inputControlColor,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
deleteBackwards?()
|
||||
AudioServicesPlaySystemSound(1155)
|
||||
|
|
@ -721,7 +721,8 @@ public final class EntityKeyboardComponent: Component {
|
|||
topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent(
|
||||
theme: component.theme,
|
||||
overflowHeight: component.hiddenInputHeight,
|
||||
topInset: component.externalTopPanelContainer == nil ? 6.0 : 0.0,
|
||||
topInset: component.externalTopPanelContainer == nil ? 8.0 : 0.0,
|
||||
height: component.externalTopPanelContainer == nil ? 40.0 : 34.0,
|
||||
displayBackground: component.externalTopPanelContainer != nil ? .none : component.displayTopPanelBackground
|
||||
)),
|
||||
externalTopPanelContainer: component.externalTopPanelContainer,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
import PagerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import BundleIconComponent
|
||||
|
|
@ -10,18 +11,18 @@ import AppBundle
|
|||
|
||||
final class EntityKeyboardBottomPanelButton: Component {
|
||||
let icon: String
|
||||
let color: UIColor
|
||||
let theme: PresentationTheme
|
||||
let action: () -> Void
|
||||
let holdAction: (() -> Void)?
|
||||
|
||||
init(
|
||||
icon: String,
|
||||
color: UIColor,
|
||||
theme: PresentationTheme,
|
||||
action: @escaping () -> Void,
|
||||
holdAction: (() -> Void)? = nil
|
||||
) {
|
||||
self.icon = icon
|
||||
self.color = color
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
self.holdAction = holdAction
|
||||
}
|
||||
|
|
@ -30,7 +31,7 @@ final class EntityKeyboardBottomPanelButton: Component {
|
|||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if (lhs.holdAction == nil) != (rhs.holdAction == nil) {
|
||||
|
|
@ -39,7 +40,9 @@ final class EntityKeyboardBottomPanelButton: Component {
|
|||
return true
|
||||
}
|
||||
|
||||
final class View: HighlightTrackingButton {
|
||||
final class View: UIView {
|
||||
private let backgroundView: GlassBackgroundView
|
||||
let buttonView: HighlightTrackingButton
|
||||
let iconView: GlassBackgroundView.ContentImageView
|
||||
let tintMaskContainer: UIView
|
||||
|
||||
|
|
@ -48,15 +51,19 @@ final class EntityKeyboardBottomPanelButton: Component {
|
|||
|
||||
var component: EntityKeyboardBottomPanelButton?
|
||||
|
||||
private var currentIsHighlighted: Bool = false {
|
||||
didSet {
|
||||
if self.currentIsHighlighted != oldValue {
|
||||
self.updateAlpha(transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
// private var currentIsHighlighted: Bool = false {
|
||||
// didSet {
|
||||
// if self.currentIsHighlighted != oldValue {
|
||||
// self.updateAlpha(transition: .immediate)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
|
||||
self.buttonView = HighlightTrackingButton()
|
||||
|
||||
self.iconView = GlassBackgroundView.ContentImageView()
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
|
||||
|
|
@ -65,9 +72,11 @@ final class EntityKeyboardBottomPanelButton: Component {
|
|||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.iconView)
|
||||
self.addSubview(self.backgroundView)
|
||||
self.backgroundView.contentView.addSubview(self.iconView)
|
||||
self.backgroundView.contentView.addSubview(self.buttonView)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
self.buttonView.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -86,65 +95,65 @@ final class EntityKeyboardBottomPanelButton: Component {
|
|||
}
|
||||
}
|
||||
|
||||
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
self.currentIsHighlighted = true
|
||||
|
||||
self.holdActionTriggerred = false
|
||||
|
||||
if self.component?.holdAction != nil {
|
||||
self.holdActionTriggerred = true
|
||||
self.component?.action()
|
||||
|
||||
self.holdActionTimer?.invalidate()
|
||||
let holdActionTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.holdActionTimer?.invalidate()
|
||||
strongSelf.component?.holdAction?()
|
||||
strongSelf.beginExecuteHoldActionTimer()
|
||||
})
|
||||
self.holdActionTimer = holdActionTimer
|
||||
RunLoop.main.add(holdActionTimer, forMode: .common)
|
||||
}
|
||||
|
||||
return super.beginTracking(touch, with: event)
|
||||
}
|
||||
// override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
// self.currentIsHighlighted = true
|
||||
//
|
||||
// self.holdActionTriggerred = false
|
||||
//
|
||||
// if self.component?.holdAction != nil {
|
||||
// self.holdActionTriggerred = true
|
||||
// self.component?.action()
|
||||
//
|
||||
// self.holdActionTimer?.invalidate()
|
||||
// let holdActionTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] _ in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.holdActionTimer?.invalidate()
|
||||
// strongSelf.component?.holdAction?()
|
||||
// strongSelf.beginExecuteHoldActionTimer()
|
||||
// })
|
||||
// self.holdActionTimer = holdActionTimer
|
||||
// RunLoop.main.add(holdActionTimer, forMode: .common)
|
||||
// }
|
||||
//
|
||||
// return super.beginTracking(touch, with: event)
|
||||
// }
|
||||
|
||||
private func beginExecuteHoldActionTimer() {
|
||||
self.holdActionTimer?.invalidate()
|
||||
let holdActionTimer = Timer(timeInterval: 0.1, repeats: true, block: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.component?.holdAction?()
|
||||
})
|
||||
self.holdActionTimer = holdActionTimer
|
||||
RunLoop.main.add(holdActionTimer, forMode: .common)
|
||||
}
|
||||
// private func beginExecuteHoldActionTimer() {
|
||||
// self.holdActionTimer?.invalidate()
|
||||
// let holdActionTimer = Timer(timeInterval: 0.1, repeats: true, block: { [weak self] _ in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.component?.holdAction?()
|
||||
// })
|
||||
// self.holdActionTimer = holdActionTimer
|
||||
// RunLoop.main.add(holdActionTimer, forMode: .common)
|
||||
// }
|
||||
|
||||
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
self.currentIsHighlighted = false
|
||||
|
||||
self.holdActionTimer?.invalidate()
|
||||
self.holdActionTimer = nil
|
||||
|
||||
super.endTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override public func cancelTracking(with event: UIEvent?) {
|
||||
self.currentIsHighlighted = false
|
||||
|
||||
self.holdActionTimer?.invalidate()
|
||||
self.holdActionTimer = nil
|
||||
|
||||
super.cancelTracking(with: event)
|
||||
}
|
||||
// override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
// self.currentIsHighlighted = false
|
||||
//
|
||||
// self.holdActionTimer?.invalidate()
|
||||
// self.holdActionTimer = nil
|
||||
//
|
||||
// super.endTracking(touch, with: event)
|
||||
// }
|
||||
//
|
||||
// override public func cancelTracking(with event: UIEvent?) {
|
||||
// self.currentIsHighlighted = false
|
||||
//
|
||||
// self.holdActionTimer?.invalidate()
|
||||
// self.holdActionTimer = nil
|
||||
//
|
||||
// super.cancelTracking(with: event)
|
||||
// }
|
||||
|
||||
private func updateAlpha(transition: ComponentTransition) {
|
||||
let alpha: CGFloat = self.currentIsHighlighted ? 0.6 : 1.0
|
||||
transition.setAlpha(view: self.iconView, alpha: alpha)
|
||||
}
|
||||
// private func updateAlpha(transition: ComponentTransition) {
|
||||
// let alpha: CGFloat = self.currentIsHighlighted ? 0.6 : 1.0
|
||||
// transition.setAlpha(view: self.iconView, alpha: alpha)
|
||||
// }
|
||||
|
||||
func update(component: EntityKeyboardBottomPanelButton, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
if self.component?.icon != component.icon {
|
||||
|
|
@ -153,15 +162,21 @@ final class EntityKeyboardBottomPanelButton: Component {
|
|||
|
||||
self.component = component
|
||||
|
||||
self.iconView.tintColor = component.color
|
||||
self.iconView.tintColor = component.theme.chat.inputPanel.panelControlColor
|
||||
|
||||
let size = CGSize(width: 38.0, height: 38.0)
|
||||
let size = CGSize(width: 40.0, height: 40.0)
|
||||
|
||||
if let image = self.iconView.image {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
||||
self.iconView.frame = iconFrame
|
||||
}
|
||||
|
||||
let tintColor: GlassBackgroundView.TintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: true, transition: transition)
|
||||
|
||||
self.buttonView.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import TelegramCore
|
|||
import ComponentDisplayAdapters
|
||||
import BundleIconComponent
|
||||
import GlassBackgroundComponent
|
||||
import EdgeEffect
|
||||
import LiquidLens
|
||||
import TabSelectionRecognizer
|
||||
|
||||
private final class BottomPanelIconComponent: Component {
|
||||
let title: String
|
||||
|
|
@ -57,22 +60,15 @@ private final class BottomPanelIconComponent: Component {
|
|||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.contentView)
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.component?.action()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: BottomPanelIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
if self.component?.title != component.title {
|
||||
let text = NSAttributedString(string: component.title, font: Font.medium(15.0), textColor: .white)
|
||||
let text = NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: .white)
|
||||
let textBounds = text.boundingRect(with: CGSize(width: 120.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
self.contentView.image = generateImage(CGSize(width: ceil(textBounds.width), height: ceil(textBounds.height)), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
|
@ -89,19 +85,9 @@ private final class BottomPanelIconComponent: Component {
|
|||
let textSize = self.contentView.image?.size ?? CGSize()
|
||||
let size = CGSize(width: textSize.width + textInset * 2.0, height: 28.0)
|
||||
|
||||
let color = component.theme.chat.inputPanel.inputControlColor
|
||||
self.contentView.tintColor = component.theme.chat.inputPanel.panelControlColor
|
||||
|
||||
if self.contentView.tintColor != color {
|
||||
if !transition.animation.isImmediate {
|
||||
UIView.animate(withDuration: 0.15, delay: 0.0, options: [], animations: {
|
||||
self.contentView.tintColor = color
|
||||
}, completion: nil)
|
||||
} else {
|
||||
self.contentView.tintColor = color
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0 - 1.0), size: textSize))
|
||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0), size: textSize))
|
||||
|
||||
return size
|
||||
}
|
||||
|
|
@ -163,17 +149,30 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
private var leftAccessoryButton: AccessoryButtonView?
|
||||
private var rightAccessoryButton: AccessoryButtonView?
|
||||
|
||||
private var iconViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let liquidLensView: LiquidLensView
|
||||
|
||||
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var selectedItemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
|
||||
private var tabSelectionRecognizer: TabSelectionRecognizer?
|
||||
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
|
||||
|
||||
private var highlightedIconBackgroundView: UIView
|
||||
private var highlightedTintIconBackgroundView: UIView
|
||||
|
||||
let tintContentMask: UIView
|
||||
|
||||
private var component: EntityKeyboardBottomPanelComponent?
|
||||
private var state: EmptyComponentState?
|
||||
private var environment: PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.tintContentMask = UIView()
|
||||
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
|
||||
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true, customBlurRadius: 10.0)
|
||||
|
||||
self.separatorView = UIView()
|
||||
|
|
@ -182,7 +181,8 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
self.tintSeparatorView.isUserInteractionEnabled = false
|
||||
self.tintSeparatorView.backgroundColor = UIColor(white: 0.0, alpha: 0.7)
|
||||
|
||||
self.tintContentMask.addSubview(self.tintSeparatorView)
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.liquidLensView = LiquidLensView(useBackgroundContainer: false)
|
||||
|
||||
self.highlightedIconBackgroundView = UIView()
|
||||
self.highlightedIconBackgroundView.isUserInteractionEnabled = false
|
||||
|
|
@ -194,39 +194,103 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
self.highlightedTintIconBackgroundView.layer.cornerRadius = 10.0
|
||||
self.highlightedTintIconBackgroundView.clipsToBounds = true
|
||||
self.highlightedTintIconBackgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.1)
|
||||
|
||||
self.tintContentMask.addSubview(self.highlightedTintIconBackgroundView)
|
||||
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addSubview(self.highlightedIconBackgroundView)
|
||||
self.addSubview(self.separatorView)
|
||||
self.addSubview(self.edgeEffectView)
|
||||
|
||||
self.addSubview(self.backgroundContainer)
|
||||
self.backgroundContainer.contentView.addSubview(self.liquidLensView)
|
||||
|
||||
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
|
||||
self.tabSelectionRecognizer = tabSelectionRecognizer
|
||||
self.liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func item(at point: CGPoint) -> AnyHashable? {
|
||||
var closestItem: (AnyHashable, CGFloat)?
|
||||
for (id, itemView) in self.itemViews {
|
||||
if itemView.frame.contains(point) {
|
||||
return id
|
||||
} else {
|
||||
let distance = abs(point.x - itemView.center.x)
|
||||
if let closestItemValue = closestItem {
|
||||
if closestItemValue.1 > distance {
|
||||
closestItem = (id, distance)
|
||||
}
|
||||
} else {
|
||||
closestItem = (id, distance)
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestItem?.0
|
||||
}
|
||||
|
||||
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
|
||||
guard let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
let location = recognizer.location(in: self.liquidLensView.contentView)
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
|
||||
let startX = itemView.frame.minX - 4.0
|
||||
self.selectionGestureState = (startX, startX, itemId)
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
}
|
||||
case .changed:
|
||||
if var selectionGestureState = self.selectionGestureState {
|
||||
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
|
||||
if let itemId = self.item(at: location) {
|
||||
selectionGestureState.itemId = itemId
|
||||
}
|
||||
self.selectionGestureState = selectionGestureState
|
||||
self.state?.updated(transition: .immediate, isLocal: true)
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
self.selectionGestureState = nil
|
||||
if case .ended = recognizer.state {
|
||||
guard let item = environment.contentIcons.first(where: { $0.id == selectionGestureState.itemId }) else {
|
||||
return
|
||||
}
|
||||
environment.navigateToContentId(item.id)
|
||||
}
|
||||
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EntityKeyboardBottomPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
if self.component?.theme !== component.theme {
|
||||
self.separatorView.backgroundColor = component.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
|
||||
self.backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate)
|
||||
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
|
||||
// self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
let intrinsicHeight: CGFloat = 34.0
|
||||
let height = intrinsicHeight + component.containerInsets.bottom
|
||||
let height = intrinsicHeight + component.containerInsets.bottom + 20.0
|
||||
|
||||
let accessoryButtonOffset: CGFloat
|
||||
if component.containerInsets.bottom > 0.0 {
|
||||
accessoryButtonOffset = 2.0
|
||||
accessoryButtonOffset = 0.0
|
||||
} else {
|
||||
accessoryButtonOffset = -2.0
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
let panelEnvironment = environment[PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>.self].value
|
||||
self.environment = panelEnvironment
|
||||
let activeContentId = panelEnvironment.activeContentId
|
||||
|
||||
var leftAccessoryButtonComponent: AnyComponentWithIdentity<Empty>?
|
||||
|
|
@ -257,7 +321,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
|
||||
)
|
||||
let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 2.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize)
|
||||
let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 18.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize)
|
||||
leftAccessoryButtonTransition.setFrame(view: leftAccessoryButton.view, frame: leftAccessoryButtonFrame)
|
||||
if let leftAccessoryButtonView = leftAccessoryButton.view.componentView as? PagerTopPanelView {
|
||||
if leftAccessoryButtonView.tintContentMask.superview == nil {
|
||||
|
|
@ -328,7 +392,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
|
||||
)
|
||||
|
||||
let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 2.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize)
|
||||
let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 18.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize)
|
||||
rightAccessoryButtonTransition.setFrame(view: rightAccessoryButton.view, frame: rightAccessoryButtonFrame)
|
||||
if let rightAccessoryButtonView = rightAccessoryButton.view.componentView as? PagerTopPanelView {
|
||||
if rightAccessoryButtonView.tintContentMask.superview == nil {
|
||||
|
|
@ -374,23 +438,37 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
var iconInfos: [AnyHashable: (size: CGSize, transition: ComponentTransition)] = [:]
|
||||
|
||||
var iconTotalSize = CGSize()
|
||||
let iconSpacing: CGFloat = 4.0
|
||||
let iconSpacing: CGFloat = 0.0
|
||||
|
||||
let navigateToContentId = panelEnvironment.navigateToContentId
|
||||
|
||||
|
||||
|
||||
//TODO:correctSize
|
||||
let tabsSize = CGSize(width: 176.0, height: 40.0)
|
||||
|
||||
var lensSelection: (x: CGFloat, width: CGFloat) = (0.0, 0.0)
|
||||
|
||||
if panelEnvironment.contentIcons.count > 1 {
|
||||
for icon in panelEnvironment.contentIcons {
|
||||
validIconIds.append(icon.id)
|
||||
|
||||
var iconTransition = transition
|
||||
let iconView: ComponentHostView<Empty>
|
||||
if let current = self.iconViews[icon.id] {
|
||||
let selectedIconView: ComponentHostView<Empty>
|
||||
if let current = self.itemViews[icon.id], let currentSelected = self.selectedItemViews[icon.id] {
|
||||
iconView = current
|
||||
selectedIconView = currentSelected
|
||||
} else {
|
||||
iconTransition = .immediate
|
||||
iconView = ComponentHostView<Empty>()
|
||||
self.iconViews[icon.id] = iconView
|
||||
self.addSubview(iconView)
|
||||
iconView.isUserInteractionEnabled = false
|
||||
selectedIconView = ComponentHostView<Empty>()
|
||||
selectedIconView.isUserInteractionEnabled = false
|
||||
self.itemViews[icon.id] = iconView
|
||||
self.selectedItemViews[icon.id] = selectedIconView
|
||||
self.liquidLensView.contentView.addSubview(iconView)
|
||||
self.liquidLensView.selectedContentView.addSubview(selectedIconView)
|
||||
}
|
||||
|
||||
let iconSize = iconView.update(
|
||||
|
|
@ -407,65 +485,90 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
containerSize: CGSize(width: 28.0, height: 28.0)
|
||||
)
|
||||
|
||||
let _ = selectedIconView.update(
|
||||
transition: iconTransition,
|
||||
component: AnyComponent(BottomPanelIconComponent(
|
||||
title: icon.title,
|
||||
isHighlighted: icon.id == activeContentId,
|
||||
theme: component.theme,
|
||||
action: {
|
||||
navigateToContentId(icon.id)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 28.0, height: 28.0)
|
||||
)
|
||||
|
||||
iconInfos[icon.id] = (size: iconSize, transition: iconTransition)
|
||||
|
||||
if !iconTotalSize.width.isZero {
|
||||
iconTotalSize.width += iconSpacing
|
||||
iconTotalSize.width += iconSpacing - 8.0
|
||||
}
|
||||
iconTotalSize.width += iconSize.width
|
||||
iconTotalSize.height = max(iconTotalSize.height, iconSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0))
|
||||
if component.containerInsets.bottom > 0.0 {
|
||||
nextIconOrigin.y += 3.0
|
||||
}
|
||||
|
||||
var nextIconOrigin = CGPoint(x: floor((tabsSize.width - iconTotalSize.width) / 2.0), y: floor((tabsSize.height - iconTotalSize.height) / 2.0))
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: .zero, size: availableSize))
|
||||
self.backgroundContainer.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
if panelEnvironment.contentIcons.count > 1 {
|
||||
for icon in panelEnvironment.contentIcons {
|
||||
guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else {
|
||||
guard let iconInfo = iconInfos[icon.id], let iconView = self.itemViews[icon.id], let selectedIconView = self.selectedItemViews[icon.id] else {
|
||||
continue
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size)
|
||||
iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil)
|
||||
iconInfo.transition.setFrame(view: selectedIconView, frame: iconFrame, completion: nil)
|
||||
|
||||
if let iconView = iconView.componentView as? BottomPanelIconComponent.View {
|
||||
if iconView.tintMaskContainer.superview == nil {
|
||||
self.tintContentMask.addSubview(iconView.tintMaskContainer)
|
||||
}
|
||||
iconInfo.transition.setFrame(view: iconView.tintMaskContainer, frame: iconFrame, completion: nil)
|
||||
}
|
||||
// if let iconView = iconView.componentView as? BottomPanelIconComponent.View {
|
||||
// if iconView.tintMaskContainer.superview == nil {
|
||||
// self.tintContentMask.addSubview(iconView.tintMaskContainer)
|
||||
// }
|
||||
// iconInfo.transition.setFrame(view: iconView.tintMaskContainer, frame: iconFrame, completion: nil)
|
||||
// }
|
||||
|
||||
// if let activeContentId = activeContentId, activeContentId == icon.id {
|
||||
// self.highlightedIconBackgroundView.isHidden = false
|
||||
// self.highlightedTintIconBackgroundView.isHidden = false
|
||||
// transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame)
|
||||
// transition.setFrame(view: self.highlightedTintIconBackgroundView, frame: iconFrame)
|
||||
//
|
||||
// let cornerRadius: CGFloat = min(iconFrame.width, iconFrame.height) / 2.0
|
||||
// transition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: cornerRadius)
|
||||
// transition.setCornerRadius(layer: self.highlightedTintIconBackgroundView.layer, cornerRadius: cornerRadius)
|
||||
// }
|
||||
if let activeContentId = activeContentId, activeContentId == icon.id {
|
||||
self.highlightedIconBackgroundView.isHidden = false
|
||||
self.highlightedTintIconBackgroundView.isHidden = false
|
||||
transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame)
|
||||
transition.setFrame(view: self.highlightedTintIconBackgroundView, frame: iconFrame)
|
||||
|
||||
let cornerRadius: CGFloat = min(iconFrame.width, iconFrame.height) / 2.0
|
||||
transition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: cornerRadius)
|
||||
transition.setCornerRadius(layer: self.highlightedTintIconBackgroundView.layer, cornerRadius: cornerRadius)
|
||||
lensSelection = (iconFrame.origin.x, iconFrame.width)
|
||||
}
|
||||
|
||||
nextIconOrigin.x += iconInfo.size.width + iconSpacing
|
||||
nextIconOrigin.x += iconInfo.size.width + iconSpacing - 8.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let selectionGestureState = self.selectionGestureState {
|
||||
lensSelection = (selectionGestureState.currentX, lensSelection.width)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabsSize.width) / 2.0), y: 0.0), size: tabsSize))
|
||||
self.liquidLensView.update(size: tabsSize, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: tabsSize.height), isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: false, transition: transition)
|
||||
|
||||
if activeContentId == nil {
|
||||
self.highlightedIconBackgroundView.isHidden = true
|
||||
}
|
||||
|
||||
var removedIconViewIds: [AnyHashable] = []
|
||||
for (id, iconView) in self.iconViews {
|
||||
for (id, iconView) in self.itemViews {
|
||||
if !validIconIds.contains(id) {
|
||||
removedIconViewIds.append(id)
|
||||
iconView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
for id in removedIconViewIds {
|
||||
self.iconViews.removeValue(forKey: id)
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.separatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
|
@ -473,8 +576,12 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
|||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: height)))
|
||||
//self.backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition)
|
||||
|
||||
|
||||
self.component = component
|
||||
let edgeEffectHeight: CGFloat = 80.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: transition)
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,18 @@ import Postbox
|
|||
|
||||
public final class EntityKeyboardTopContainerPanelEnvironment: Equatable {
|
||||
let isContentInFocus: Bool
|
||||
let height: CGFloat
|
||||
let visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)>
|
||||
let isExpandedUpdated: (Bool, ComponentTransition) -> Void
|
||||
|
||||
init(
|
||||
isContentInFocus: Bool,
|
||||
height: CGFloat,
|
||||
visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)>,
|
||||
isExpandedUpdated: @escaping (Bool, ComponentTransition) -> Void
|
||||
) {
|
||||
self.isContentInFocus = isContentInFocus
|
||||
self.height = height
|
||||
self.visibilityFractionUpdated = visibilityFractionUpdated
|
||||
self.isExpandedUpdated = isExpandedUpdated
|
||||
}
|
||||
|
|
@ -26,6 +29,9 @@ public final class EntityKeyboardTopContainerPanelEnvironment: Equatable {
|
|||
if lhs.isContentInFocus != rhs.isContentInFocus {
|
||||
return false
|
||||
}
|
||||
if lhs.height != rhs.height {
|
||||
return false
|
||||
}
|
||||
if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated {
|
||||
return false
|
||||
}
|
||||
|
|
@ -39,17 +45,20 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
|||
let theme: PresentationTheme
|
||||
let overflowHeight: CGFloat
|
||||
let topInset: CGFloat
|
||||
let height: CGFloat
|
||||
let displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
overflowHeight: CGFloat,
|
||||
topInset: CGFloat,
|
||||
height: CGFloat,
|
||||
displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
|
||||
) {
|
||||
self.theme = theme
|
||||
self.overflowHeight = overflowHeight
|
||||
self.topInset = topInset
|
||||
self.height = height
|
||||
self.displayBackground = displayBackground
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +69,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
|||
if lhs.overflowHeight != rhs.overflowHeight {
|
||||
return false
|
||||
}
|
||||
if lhs.height != rhs.height {
|
||||
return false
|
||||
}
|
||||
if lhs.topInset != rhs.topInset {
|
||||
return false
|
||||
}
|
||||
|
|
@ -105,7 +117,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
|||
}
|
||||
|
||||
func update(component: EntityKeyboardTopContainerPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
let intrinsicHeight: CGFloat = 34.0
|
||||
let intrinsicHeight: CGFloat = component.height
|
||||
let height = intrinsicHeight + component.topInset
|
||||
|
||||
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value
|
||||
|
|
@ -165,6 +177,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
|||
environment: {
|
||||
EntityKeyboardTopContainerPanelEnvironment(
|
||||
isContentInFocus: panelEnvironment.isContentInFocus,
|
||||
height: intrinsicHeight,
|
||||
visibilityFractionUpdated: panelView.visibilityFractionUpdated,
|
||||
isExpandedUpdated: { [weak self] isExpanded, transition in
|
||||
guard let strongSelf = self else {
|
||||
|
|
|
|||
|
|
@ -2009,7 +2009,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
|
|||
let panelEnvironment = environment[EntityKeyboardTopContainerPanelEnvironment.self].value
|
||||
self.environment = panelEnvironment
|
||||
|
||||
let isExpanded = availableSize.height > 34.0
|
||||
let isExpanded = availableSize.height > panelEnvironment.height
|
||||
let wasExpanded = self.isExpanded
|
||||
self.isExpanded = isExpanded
|
||||
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
|||
if self.backgroundHandleView.image == nil {
|
||||
self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.backgroundHandleView.tintColor = environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(environment.theme.overallDarkAppearance ? 0.2 : 0.07)
|
||||
self.backgroundHandleView.tintColor = theme.list.itemPrimaryTextColor.withMultipliedAlpha(theme.overallDarkAppearance ? 0.2 : 0.07)
|
||||
let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0))
|
||||
if self.backgroundHandleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(self.backgroundHandleView)
|
||||
|
|
@ -383,13 +383,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
|||
transition: .immediate,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: environment.theme.chat.inputPanel.panelControlColor
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
|
|
@ -413,14 +413,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
|||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
contentHeight += environment.safeInsets.bottom
|
||||
|
||||
var initialContentHeight = contentHeight
|
||||
let clippingY: CGFloat
|
||||
|
||||
let titleText: String = environment.strings.Gift_ActiveAuctions_Title(Int32(self.auctionStates.count))
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
|
|
@ -434,12 +433,12 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
|||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
|
||||
initialContentHeight = contentHeight
|
||||
let initialContentHeight = contentHeight
|
||||
|
||||
let edgeEffectHeight: CGFloat = 80.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.topEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
self.topEdgeEffectView.update(content: theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
if self.topEdgeEffectView.superview == nil {
|
||||
self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeerTableCellComponent",
|
||||
module_name = "PeerTableCellComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AvatarNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import MultilineTextComponent
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
|
||||
public final class PeerTableCellComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer?
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
}
|
||||
|
||||
public static func ==(lhs: PeerTableCellComponent, rhs: PeerTableCellComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let avatarNode: AvatarNode
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private var component: PeerTableCellComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: PeerTableCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let avatarSize = CGSize(width: 22.0, height: 22.0)
|
||||
let spacing: CGFloat = 6.0
|
||||
|
||||
var peerName: String
|
||||
let avatarOverride: AvatarNodeImageOverride?
|
||||
if let peerValue = component.peer {
|
||||
peerName = peerValue.compactDisplayTitle
|
||||
if peerName.count > 40 {
|
||||
peerName = "\(peerName.prefix(40))…"
|
||||
}
|
||||
avatarOverride = nil
|
||||
} else {
|
||||
peerName = component.strings.Gift_View_HiddenName
|
||||
avatarOverride = .anonymousSavedMessagesIcon(isColored: true)
|
||||
}
|
||||
|
||||
let avatarNaturalSize = CGSize(width: 40.0, height: 40.0)
|
||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride)
|
||||
self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left))
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height)
|
||||
)
|
||||
|
||||
let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height)
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize)
|
||||
self.avatarNode.frame = avatarFrame
|
||||
|
||||
if let view = self.text.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
transition.setFrame(view: view, frame: textFrame)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public 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)
|
||||
}
|
||||
}
|
||||
23
submodules/TelegramUI/Components/Gifts/TableComponent/BUILD
Normal file
23
submodules/TelegramUI/Components/Gifts/TableComponent/BUILD
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "TableComponent",
|
||||
module_name = "TableComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import MultilineTextComponent
|
||||
|
||||
public final class TableComponent: CombinedComponent {
|
||||
public class Item: Equatable {
|
||||
public enum TitleFont {
|
||||
case regular
|
||||
case bold
|
||||
}
|
||||
|
||||
public let id: AnyHashable
|
||||
public let title: String?
|
||||
public let titleFont: TitleFont
|
||||
public let hasBackground: Bool
|
||||
public let component: AnyComponent<Empty>
|
||||
public let insets: UIEdgeInsets?
|
||||
|
||||
public init<IdType: Hashable>(
|
||||
id: IdType,
|
||||
title: String?,
|
||||
titleFont: TitleFont = .regular,
|
||||
hasBackground: Bool = false,
|
||||
component: AnyComponent<Empty>,
|
||||
insets: UIEdgeInsets? = nil
|
||||
) {
|
||||
self.id = AnyHashable(id)
|
||||
self.title = title
|
||||
self.titleFont = titleFont
|
||||
self.hasBackground = hasBackground
|
||||
self.component = component
|
||||
self.insets = insets
|
||||
}
|
||||
|
||||
public static func == (lhs: Item, rhs: Item) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.titleFont != rhs.titleFont {
|
||||
return false
|
||||
}
|
||||
if lhs.hasBackground != rhs.hasBackground {
|
||||
return false
|
||||
}
|
||||
if lhs.component != rhs.component {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let theme: PresentationTheme
|
||||
private let items: [Item]
|
||||
private let semiTransparent: Bool
|
||||
|
||||
public init(theme: PresentationTheme, items: [Item], semiTransparent: Bool = false) {
|
||||
self.theme = theme
|
||||
self.items = items
|
||||
self.semiTransparent = semiTransparent
|
||||
}
|
||||
|
||||
public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.semiTransparent != rhs.semiTransparent {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class State: ComponentState {
|
||||
var cachedLastBackgroundImage: (UIImage, PresentationTheme)?
|
||||
var cachedLeftColumnImage: (UIImage, PresentationTheme)?
|
||||
var cachedBorderImage: (UIImage, PresentationTheme)?
|
||||
}
|
||||
|
||||
public func makeState() -> State {
|
||||
return State()
|
||||
}
|
||||
|
||||
public static var body: Body {
|
||||
let leftColumnBackground = Child(Image.self)
|
||||
let lastBackground = Child(Image.self)
|
||||
let verticalBorder = Child(Rectangle.self)
|
||||
let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||
let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||
let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||
let outerBorder = Child(Image.self)
|
||||
|
||||
return { context in
|
||||
let verticalPadding: CGFloat = 11.0
|
||||
let horizontalPadding: CGFloat = 12.0
|
||||
let borderWidth: CGFloat = 1.0
|
||||
|
||||
let borderColor: UIColor
|
||||
let secondaryBackgroundColor: UIColor
|
||||
if context.component.semiTransparent {
|
||||
borderColor = context.component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1)
|
||||
secondaryBackgroundColor = context.component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.05)
|
||||
} else {
|
||||
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
|
||||
borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
|
||||
secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor
|
||||
}
|
||||
|
||||
var leftColumnWidth: CGFloat = 0.0
|
||||
|
||||
var updatedTitleChildren: [Int: _UpdatedChildComponent] = [:]
|
||||
var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = []
|
||||
var updatedBorderChildren: [_UpdatedChildComponent] = []
|
||||
|
||||
var i = 0
|
||||
for item in context.component.items {
|
||||
guard let title = item.title else {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
let titleChild = titleChildren[item.id].update(
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: title, font: item.titleFont == .bold ? Font.semibold(15.0) : Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
updatedTitleChildren[i] = titleChild
|
||||
|
||||
if titleChild.size.width > leftColumnWidth {
|
||||
leftColumnWidth = titleChild.size.width
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0)
|
||||
let rightColumnWidth = context.availableSize.width - leftColumnWidth
|
||||
|
||||
i = 0
|
||||
var rowHeights: [Int: CGFloat] = [:]
|
||||
var totalHeight: CGFloat = 0.0
|
||||
var innerTotalHeight: CGFloat = 0.0
|
||||
var innerTotalOffset: CGFloat = 0.0
|
||||
var hasRowBackground = false
|
||||
var rowBackgroundIsLast = false
|
||||
var hasStraightSide = false
|
||||
|
||||
for item in context.component.items {
|
||||
let insets: UIEdgeInsets
|
||||
if let customInsets = item.insets {
|
||||
insets = customInsets
|
||||
} else {
|
||||
insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding)
|
||||
}
|
||||
|
||||
var titleHeight: CGFloat = 0.0
|
||||
if let titleChild = updatedTitleChildren[i] {
|
||||
titleHeight = titleChild.size.height
|
||||
}
|
||||
|
||||
let availableValueWidth: CGFloat
|
||||
if titleHeight > 0.0 {
|
||||
availableValueWidth = rightColumnWidth
|
||||
} else {
|
||||
availableValueWidth = context.availableSize.width
|
||||
}
|
||||
|
||||
let valueChild = valueChildren[item.id].update(
|
||||
component: item.component,
|
||||
availableSize: CGSize(width: availableValueWidth - insets.left - insets.right, height: context.availableSize.height),
|
||||
transition: context.transition
|
||||
)
|
||||
updatedValueChildren.append((valueChild, insets))
|
||||
|
||||
let rowHeight = max(40.0, max(titleHeight, valueChild.size.height) + verticalPadding * 2.0)
|
||||
rowHeights[i] = rowHeight
|
||||
totalHeight += rowHeight
|
||||
if titleHeight > 0.0 {
|
||||
innerTotalHeight += rowHeight
|
||||
} else if i == 0 {
|
||||
innerTotalOffset += rowHeight
|
||||
}
|
||||
|
||||
if i < context.component.items.count - 1 {
|
||||
let borderChild = borderChildren[item.id].update(
|
||||
component: AnyComponent(Rectangle(color: borderColor)),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: borderWidth),
|
||||
transition: context.transition
|
||||
)
|
||||
updatedBorderChildren.append(borderChild)
|
||||
}
|
||||
|
||||
if item.hasBackground {
|
||||
if i != 0 {
|
||||
rowBackgroundIsLast = true
|
||||
}
|
||||
hasRowBackground = true
|
||||
}
|
||||
if item.title == nil {
|
||||
if i != 0 {
|
||||
rowBackgroundIsLast = true
|
||||
}
|
||||
hasStraightSide = true
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
let borderRadius: CGFloat = 14.0
|
||||
|
||||
if hasRowBackground {
|
||||
let lastBackgroundImage: UIImage
|
||||
if let (currentImage, theme) = context.state.cachedLastBackgroundImage, theme === context.component.theme {
|
||||
lastBackgroundImage = currentImage
|
||||
} else {
|
||||
lastBackgroundImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.height + borderRadius))
|
||||
context.clear(bounds)
|
||||
|
||||
let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0).insetBy(dx: 0.0, dy: rowBackgroundIsLast ? -borderRadius * 2.0 : 0.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil)
|
||||
context.setFillColor(secondaryBackgroundColor.cgColor)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
})!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius))
|
||||
context.state.cachedLastBackgroundImage = (lastBackgroundImage, context.component.theme)
|
||||
}
|
||||
|
||||
let lastRowHeight: CGFloat
|
||||
let position: CGFloat
|
||||
if !rowBackgroundIsLast {
|
||||
lastRowHeight = rowHeights[0] ?? 0
|
||||
position = lastRowHeight / 2.0
|
||||
} else {
|
||||
lastRowHeight = rowHeights[i - 1] ?? 0
|
||||
position = totalHeight - lastRowHeight / 2.0
|
||||
}
|
||||
let lastBackground = lastBackground.update(
|
||||
component: Image(image: lastBackgroundImage),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: lastRowHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(
|
||||
lastBackground
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: position))
|
||||
)
|
||||
}
|
||||
|
||||
let leftColumnImage: UIImage
|
||||
if let (currentImage, theme) = context.state.cachedLeftColumnImage, theme === context.component.theme {
|
||||
leftColumnImage = currentImage
|
||||
} else {
|
||||
leftColumnImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in
|
||||
var bounds = CGRect(origin: .zero, size: CGSize(width: size.width + borderRadius, height: size.height))
|
||||
context.clear(bounds)
|
||||
|
||||
var offset: CGFloat = 0.0
|
||||
if hasStraightSide {
|
||||
offset = rowBackgroundIsLast ? 0.0 : -borderRadius
|
||||
|
||||
bounds.origin.y += offset
|
||||
bounds.size.height += borderRadius
|
||||
}
|
||||
|
||||
let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil)
|
||||
context.setFillColor(secondaryBackgroundColor.cgColor)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
})!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius))
|
||||
context.state.cachedLeftColumnImage = (leftColumnImage, context.component.theme)
|
||||
}
|
||||
|
||||
let leftColumnBackground = leftColumnBackground.update(
|
||||
component: Image(image: leftColumnImage),
|
||||
availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(leftColumnBackground
|
||||
.position(CGPoint(x: leftColumnWidth / 2.0, y: innerTotalOffset + innerTotalHeight / 2.0))
|
||||
)
|
||||
|
||||
let borderImage: UIImage
|
||||
if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme {
|
||||
borderImage = currentImage
|
||||
} else {
|
||||
borderImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil)
|
||||
context.setBlendMode(.clear)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
context.setStrokeColor(borderColor.cgColor)
|
||||
context.setLineWidth(borderWidth)
|
||||
context.addPath(path)
|
||||
context.strokePath()
|
||||
})!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius))
|
||||
context.state.cachedBorderImage = (borderImage, context.component.theme)
|
||||
}
|
||||
|
||||
let outerBorder = outerBorder.update(
|
||||
component: Image(image: borderImage),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: totalHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(outerBorder
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0))
|
||||
)
|
||||
|
||||
let verticalBorder = verticalBorder.update(
|
||||
component: Rectangle(color: borderColor),
|
||||
availableSize: CGSize(width: borderWidth, height: innerTotalHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(
|
||||
verticalBorder
|
||||
.position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: innerTotalOffset + innerTotalHeight / 2.0))
|
||||
)
|
||||
|
||||
i = 0
|
||||
var originY: CGFloat = 0.0
|
||||
for (valueChild, valueInsets) in updatedValueChildren {
|
||||
let rowHeight = rowHeights[i] ?? 0.0
|
||||
|
||||
let valueFrame: CGRect
|
||||
if let titleChild = updatedTitleChildren[i] {
|
||||
let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size)
|
||||
context.add(titleChild
|
||||
.position(titleFrame.center)
|
||||
)
|
||||
valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size)
|
||||
} else {
|
||||
if hasRowBackground && rowBackgroundIsLast {
|
||||
valueFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - valueChild.size.width) / 2.0), y: originY + verticalPadding), size: valueChild.size)
|
||||
} else {
|
||||
valueFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: valueChild.size)
|
||||
}
|
||||
}
|
||||
|
||||
context.add(valueChild
|
||||
.position(valueFrame.center)
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
|
||||
if i < updatedBorderChildren.count {
|
||||
let borderChild = updatedBorderChildren[i]
|
||||
context.add(borderChild
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
}
|
||||
|
||||
originY += rowHeight
|
||||
i += 1
|
||||
}
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: totalHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,16 +60,16 @@ private final class RestingBackgroundView: UIVisualEffectView {
|
|||
public final class LiquidLensView: UIView {
|
||||
private struct Params: Equatable {
|
||||
var size: CGSize
|
||||
var selectionX: CGFloat
|
||||
var selectionWidth: CGFloat
|
||||
var selectionOrigin: CGPoint
|
||||
var selectionSize: CGSize
|
||||
var isDark: Bool
|
||||
var isLifted: Bool
|
||||
var isCollapsed: Bool
|
||||
|
||||
init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) {
|
||||
init(size: CGSize, selectionOrigin: CGPoint, selectionSize: CGSize, isDark: Bool, isLifted: Bool, isCollapsed: Bool) {
|
||||
self.size = size
|
||||
self.selectionX = selectionX
|
||||
self.selectionWidth = selectionWidth
|
||||
self.selectionOrigin = selectionOrigin
|
||||
self.selectionSize = selectionSize
|
||||
self.isLifted = isLifted
|
||||
self.isDark = isDark
|
||||
self.isCollapsed = isCollapsed
|
||||
|
|
@ -111,12 +111,12 @@ public final class LiquidLensView: UIView {
|
|||
|
||||
private var liftedDisplayLink: SharedDisplayLinkDriver.Link?
|
||||
|
||||
public var selectionX: CGFloat? {
|
||||
return self.params?.selectionX
|
||||
public var selectionOrigin: CGPoint? {
|
||||
return self.params?.selectionOrigin
|
||||
}
|
||||
|
||||
public var selectionWidth: CGFloat? {
|
||||
return self.params?.selectionWidth
|
||||
public var selectionSize: CGSize? {
|
||||
return self.params?.selectionSize
|
||||
}
|
||||
|
||||
public init(useBackgroundContainer: Bool = true) {
|
||||
|
|
@ -242,8 +242,8 @@ public final class LiquidLensView: UIView {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) {
|
||||
let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed)
|
||||
public func update(size: CGSize, selectionOrigin: CGPoint, selectionSize: CGSize, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) {
|
||||
let params = Params(size: size, selectionOrigin: selectionOrigin, selectionSize: selectionSize, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed)
|
||||
if self.params == params {
|
||||
return
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ public final class LiquidLensView: UIView {
|
|||
transition.setCornerRadius(layer: self.liftedContainerView.layer, cornerRadius: params.size.height * 0.5)
|
||||
}
|
||||
|
||||
let baseLensFrame = CGRect(origin: CGPoint(x: max(0.0, min(params.selectionX, params.size.width - params.selectionWidth)), y: 0.0), size: CGSize(width: params.selectionWidth, height: params.size.height))
|
||||
let baseLensFrame = CGRect(origin: CGPoint(x: max(0.0, min(params.selectionOrigin.x, params.size.width - params.selectionSize.width)), y: params.selectionOrigin.y), size: CGSize(width: params.selectionSize.width, height: params.size.height))
|
||||
self.updateLens(params: LensParams(baseFrame: baseLensFrame, isLifted: params.isLifted), animated: !transition.animation.isImmediate)
|
||||
|
||||
if let legacyContentMaskView = self.legacyContentMaskView {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ swift_library(
|
|||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
|
|
@ -32,6 +33,8 @@ swift_library(
|
|||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/ShareController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ import Markdown
|
|||
import TelegramStringFormatting
|
||||
import MessagePriceItem
|
||||
import ListItemComponentAdaptor
|
||||
import ButtonComponent
|
||||
import PlainButtonComponent
|
||||
import UndoUI
|
||||
import ShareController
|
||||
|
||||
final class PostSuggestionsSettingsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -68,6 +72,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
|||
private let subtitle = ComponentView<Empty>()
|
||||
private let switchSection = ComponentView<Empty>()
|
||||
private let contentSection = ComponentView<Empty>()
|
||||
private let linkSection = ComponentView<Empty>()
|
||||
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
|
|
@ -166,6 +171,91 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
func dismissAllTooltips() {
|
||||
guard let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
controller.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func copyLink(_ link: String) {
|
||||
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
|
||||
func shareLink(_ link: String) {
|
||||
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = component.context
|
||||
let shareController = ShareController(context: context, subject: .url(link), updatedPresentationData: nil)
|
||||
shareController.completed = { [weak controller] peerIds in
|
||||
let _ = (context.engine.data.get(
|
||||
EngineDataList(
|
||||
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
||||
)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] peerList in
|
||||
let peers = peerList.compactMap { $0 }
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
var savedMessages = false
|
||||
if peerIds.count == 1, let peerId = peerIds.first, peerId == context.account.peerId {
|
||||
text = presentationData.strings.InviteLink_InviteLinkForwardTooltip_SavedMessages_One
|
||||
savedMessages = true
|
||||
} else {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
let peerName = peer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_Chat_One(peerName).string
|
||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||
let firstPeerName = firstPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let secondPeerName = secondPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_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.UserInfo_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
|
||||
controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in
|
||||
if savedMessages, action == .info {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), forceOpenChat: true))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
controller.present(shareController, in: .window(.root))
|
||||
}
|
||||
|
||||
func update(component: PostSuggestionsSettingsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
|
|
@ -429,6 +519,50 @@ final class PostSuggestionsSettingsScreenComponent: Component {
|
|||
|
||||
if self.areSuggestionsEnabled {
|
||||
contentHeight += contentSectionSize.height
|
||||
contentHeight += sectionSpacing
|
||||
}
|
||||
|
||||
let address = component.peer?.addressName ?? ""
|
||||
let link = "t.me/\(address)?direct"
|
||||
let fullLink = "https://\(link)"
|
||||
var linkSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
linkSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||
LinkComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
link: link,
|
||||
copyAction: { [weak self] in
|
||||
self?.copyLink(fullLink)
|
||||
},
|
||||
shareAction: { [weak self] in
|
||||
self?.shareLink(fullLink)
|
||||
}
|
||||
)
|
||||
)))
|
||||
let linkSectionSize = self.linkSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
style: .glass,
|
||||
header: nil,
|
||||
footer: nil,
|
||||
items: linkSectionItems
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let linkSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: linkSectionSize)
|
||||
if let linkSectionView = self.linkSection.view {
|
||||
if linkSectionView.superview == nil {
|
||||
self.scrollView.addSubview(linkSectionView)
|
||||
self.linkSection.parentState = state
|
||||
}
|
||||
transition.setFrame(view: linkSectionView, frame: linkSectionFrame)
|
||||
alphaTransition.setAlpha(view: linkSectionView, alpha: self.areSuggestionsEnabled && !address.isEmpty ? 1.0 : 0.0)
|
||||
}
|
||||
if self.areSuggestionsEnabled && !address.isEmpty {
|
||||
contentHeight += switchSectionSize.height
|
||||
contentHeight += sectionSpacing
|
||||
}
|
||||
|
||||
contentHeight += bottomContentInset
|
||||
|
|
@ -546,3 +680,323 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain
|
|||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class LinkContentComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let link: String
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
link: String
|
||||
) {
|
||||
self.theme = theme
|
||||
self.link = link
|
||||
}
|
||||
|
||||
static func ==(lhs: LinkContentComponent, rhs: LinkContentComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.link != rhs.link {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: LinkContentComponent?
|
||||
|
||||
private let background = ComponentView<Empty>()
|
||||
private let link = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: LinkContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let padding: CGFloat = 10.0
|
||||
|
||||
let backgroundSize = self.background.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
FilledRoundedRectangleComponent(
|
||||
color: component.theme.list.itemInputField.backgroundColor,
|
||||
cornerRadius: .minEdge,
|
||||
smoothCorners: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
|
||||
if let backgroundView = self.background.view {
|
||||
if backgroundView.superview == nil {
|
||||
self.addSubview(backgroundView)
|
||||
}
|
||||
transition.setFrame(view: backgroundView, frame: backgroundFrame)
|
||||
}
|
||||
|
||||
let linkFont = Font.regular(17.0)
|
||||
let linkSize = self.link.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.link, font: linkFont, textColor: component.theme.list.itemPrimaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 2
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - padding * 4.0, height: availableSize.height)
|
||||
)
|
||||
let linkFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - linkSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - linkSize.height) / 2.0) - UIScreenPixel), size: linkSize)
|
||||
if let linkView = self.link.view {
|
||||
if linkView.superview == nil {
|
||||
self.addSubview(linkView)
|
||||
}
|
||||
transition.setFrame(view: linkView, frame: linkFrame)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private final class LinkComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let link: String
|
||||
let copyAction: () -> Void
|
||||
let shareAction: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
link: String,
|
||||
copyAction: @escaping () -> Void,
|
||||
shareAction: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.link = link
|
||||
self.copyAction = copyAction
|
||||
self.shareAction = shareAction
|
||||
}
|
||||
|
||||
static func ==(lhs: LinkComponent, rhs: LinkComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.link != rhs.link {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let linkButton = ComponentView<Empty>()
|
||||
private let moreButton = ComponentView<Empty>()
|
||||
private var copyButton = ComponentView<Empty>()
|
||||
private var shareButton = ComponentView<Empty>()
|
||||
|
||||
private var component: LinkComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var cachedMoreImage: (UIImage, PresentationTheme)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: LinkComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
var contentHeight: CGFloat = sideInset
|
||||
|
||||
let linkButtonSize = self.linkButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(LinkContentComponent(theme: component.theme, link: component.link)),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.copyAction()
|
||||
},
|
||||
animateScale: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0)
|
||||
)
|
||||
let linkButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: linkButtonSize)
|
||||
if let linkButtonView = self.linkButton.view {
|
||||
if linkButtonView.superview == nil {
|
||||
self.addSubview(linkButtonView)
|
||||
}
|
||||
linkButtonView.frame = linkButtonFrame
|
||||
}
|
||||
|
||||
let moreButtonImage: UIImage
|
||||
if let (image, theme) = self.cachedMoreImage, theme === component.theme {
|
||||
moreButtonImage = image
|
||||
} else {
|
||||
moreButtonImage = actionButtonImage(color: component.theme.list.itemInputField.controlColor)!
|
||||
self.cachedMoreImage = (moreButtonImage, component.theme)
|
||||
}
|
||||
|
||||
let moreButtonSize = self.moreButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(Image(image: moreButtonImage, contentMode: .center)),
|
||||
minSize: CGSize(width: 52.0, height: 52.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.copyAction()
|
||||
},
|
||||
animateScale: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 52.0, height: 52.0)
|
||||
)
|
||||
let moreButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - moreButtonSize.width, y: contentHeight), size: moreButtonSize)
|
||||
if let moreButtonView = self.moreButton.view {
|
||||
if moreButtonView.superview == nil {
|
||||
self.addSubview(moreButtonView)
|
||||
}
|
||||
moreButtonView.frame = moreButtonFrame
|
||||
}
|
||||
|
||||
contentHeight += linkButtonSize.height
|
||||
contentHeight += 10.0
|
||||
|
||||
var buttonWidth = availableSize.width - sideInset * 2.0
|
||||
buttonWidth = (buttonWidth - 10.0) / 2.0
|
||||
|
||||
let copyButtonSize = self.copyButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "Copy", font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.copyAction()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: buttonWidth, height: 52.0)
|
||||
)
|
||||
let copyButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: copyButtonSize)
|
||||
if let copyButtonView = self.copyButton.view {
|
||||
if copyButtonView.superview == nil {
|
||||
self.addSubview(copyButtonView)
|
||||
}
|
||||
copyButtonView.frame = copyButtonFrame
|
||||
}
|
||||
|
||||
let shareButtonSize = self.shareButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "Share", font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.shareAction()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: buttonWidth, height: 52.0)
|
||||
)
|
||||
let shareButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - shareButtonSize.width, y: contentHeight), size: shareButtonSize)
|
||||
if let shareButtonView = self.shareButton.view {
|
||||
if shareButtonView.superview == nil {
|
||||
self.addSubview(shareButtonView)
|
||||
}
|
||||
shareButtonView.frame = shareButtonFrame
|
||||
}
|
||||
|
||||
contentHeight += copyButtonSize.height
|
||||
contentHeight += sideInset
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func stringForRemainingTime(_ duration: Int32) -> String {
|
||||
let hours = duration / 3600
|
||||
let minutes = duration / 60 % 60
|
||||
let seconds = duration % 60
|
||||
let durationString: String
|
||||
if hours > 0 {
|
||||
durationString = String(format: "%d:%02d", hours, minutes)
|
||||
} else {
|
||||
durationString = String(format: "%02d:%02d", minutes, seconds)
|
||||
}
|
||||
return durationString
|
||||
}
|
||||
|
||||
private func actionButtonImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0)))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import TelegramStringFormatting
|
|||
import BundleIconComponent
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import GlassBarButtonComponent
|
||||
|
||||
private final class BalanceNeededSheetContentComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -49,9 +50,7 @@ private final class BalanceNeededSheetContentComponent: Component {
|
|||
|
||||
private var component: BalanceNeededSheetContentComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
|
@ -71,28 +70,27 @@ private final class BalanceNeededSheetContentComponent: Component {
|
|||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let closeImage: UIImage
|
||||
if let (image, theme) = self.cachedCloseImage, theme === environment.theme {
|
||||
closeImage = image
|
||||
} else {
|
||||
closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: environment.theme.actionSheet.inputClearButtonColor)!
|
||||
self.cachedCloseImage = (closeImage, environment.theme)
|
||||
}
|
||||
let closeButtonSize = self.closeButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Image(image: closeImage)),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: environment.theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
component.dismiss()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - closeButtonSize.width - 16.0, y: 12.0), size: closeButtonSize)
|
||||
let closeButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize)
|
||||
if let closeButtonView = self.closeButton.view {
|
||||
if closeButtonView.superview == nil {
|
||||
self.addSubview(closeButtonView)
|
||||
|
|
@ -164,10 +162,12 @@ private final class BalanceNeededSheetContentComponent: Component {
|
|||
contentHeight += textSize.height
|
||||
contentHeight += 24.0
|
||||
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let buttonSize = self.button.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: environment.theme.list.itemCheckColors.fillColor,
|
||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||
|
|
@ -187,9 +187,9 @@ private final class BalanceNeededSheetContentComponent: Component {
|
|||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||
containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
|
|
@ -197,13 +197,8 @@ private final class BalanceNeededSheetContentComponent: Component {
|
|||
transition.setFrame(view: buttonView, frame: buttonFrame)
|
||||
}
|
||||
contentHeight += buttonSize.height
|
||||
|
||||
if environment.safeInsets.bottom.isZero {
|
||||
contentHeight += 16.0
|
||||
} else {
|
||||
contentHeight += environment.safeInsets.bottom + 8.0
|
||||
}
|
||||
|
||||
contentHeight += buttonInsets.bottom
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
|
@ -307,6 +302,7 @@ private final class BalanceNeededScreenComponent: Component {
|
|||
})
|
||||
}
|
||||
)),
|
||||
style: .glass,
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
animateOut: self.sheetAnimateOut
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/LiquidLens",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/SearchBarNode",
|
||||
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -13,62 +13,7 @@ import TextBadgeComponent
|
|||
import LiquidLens
|
||||
import AppBundle
|
||||
import SearchBarNode
|
||||
|
||||
private final class TabSelectionRecognizer: UIGestureRecognizer {
|
||||
private var initialLocation: CGPoint?
|
||||
private var currentLocation: CGPoint?
|
||||
|
||||
override init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
self.delaysTouchesBegan = false
|
||||
self.delaysTouchesEnded = false
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.initialLocation = nil
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if self.initialLocation == nil {
|
||||
self.initialLocation = touches.first?.location(in: self.view)
|
||||
}
|
||||
self.currentLocation = self.initialLocation
|
||||
|
||||
self.state = .began
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.state = .ended
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.state = .cancelled
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
self.currentLocation = touches.first?.location(in: self.view)
|
||||
|
||||
self.state = .changed
|
||||
}
|
||||
|
||||
func translation(in: UIView?) -> CGPoint {
|
||||
if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation {
|
||||
return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y)
|
||||
}
|
||||
return CGPoint()
|
||||
}
|
||||
}
|
||||
import TabSelectionRecognizer
|
||||
|
||||
public final class NavigationSearchView: UIView {
|
||||
private struct Params: Equatable {
|
||||
|
|
@ -771,7 +716,7 @@ public final class TabBarComponent: Component {
|
|||
lensSize = CGSize(width: 48.0, height: 48.0)
|
||||
lensSelection = (0.0, 48.0)
|
||||
}
|
||||
self.liquidLensView.update(size: lensSize, selectionX: lensSelection.x, selectionWidth: lensSelection.width, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isLensCollapsed, transition: transition)
|
||||
self.liquidLensView.update(size: lensSize, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: lensSize.height), isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isLensCollapsed, transition: transition)
|
||||
|
||||
var size = tabsSize
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "TabSelectionRecognizer",
|
||||
module_name = "TabSelectionRecognizer",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class TabSelectionRecognizer: UIGestureRecognizer {
|
||||
private var initialLocation: CGPoint?
|
||||
private var currentLocation: CGPoint?
|
||||
|
||||
public override init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
self.delaysTouchesBegan = false
|
||||
self.delaysTouchesEnded = false
|
||||
}
|
||||
|
||||
public override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.initialLocation = nil
|
||||
}
|
||||
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if self.initialLocation == nil {
|
||||
self.initialLocation = touches.first?.location(in: self.view)
|
||||
}
|
||||
self.currentLocation = self.initialLocation
|
||||
|
||||
self.state = .began
|
||||
}
|
||||
|
||||
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.state = .ended
|
||||
}
|
||||
|
||||
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.state = .cancelled
|
||||
}
|
||||
|
||||
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
self.currentLocation = touches.first?.location(in: self.view)
|
||||
|
||||
self.state = .changed
|
||||
}
|
||||
|
||||
public func translation(in: UIView?) -> CGPoint {
|
||||
if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation {
|
||||
return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y)
|
||||
}
|
||||
return CGPoint()
|
||||
}
|
||||
}
|
||||
|
|
@ -106,7 +106,12 @@ public final class TextNodeWithEntities {
|
|||
public let textNode: TextNode
|
||||
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
|
||||
|
||||
private var enableLooping: Bool = true
|
||||
public var enableLooping: Bool = true
|
||||
public var energySavingEnableLooping: Bool = true
|
||||
|
||||
private var effectiveEnableLooping: Bool {
|
||||
return self.enableLooping && self.energySavingEnableLooping
|
||||
}
|
||||
|
||||
public var resetEmojiToFirstFrameAutomatically: Bool = false
|
||||
|
||||
|
|
@ -124,7 +129,7 @@ public final class TextNodeWithEntities {
|
|||
} else {
|
||||
isItemVisible = false
|
||||
}
|
||||
let isVisibleForAnimations = self.enableLooping && isItemVisible && itemLayer.enableAnimation
|
||||
let isVisibleForAnimations = self.effectiveEnableLooping && isItemVisible && itemLayer.enableAnimation
|
||||
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||
itemLayer.isVisibleForAnimations = isVisibleForAnimations
|
||||
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||
|
|
@ -257,7 +262,7 @@ public final class TextNodeWithEntities {
|
|||
}
|
||||
|
||||
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool, emojiOffset: CGPoint, fontSizeNorm: CGFloat) {
|
||||
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||
self.energySavingEnableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||
|
||||
var nextIndexById: [Int64: Int] = [:]
|
||||
var validIds: [InlineStickerItemLayer.Key] = []
|
||||
|
|
@ -292,7 +297,7 @@ public final class TextNodeWithEntities {
|
|||
self.textNode.layer.addSublayer(itemLayer)
|
||||
}
|
||||
itemLayer.enableAnimation = stickerItem.enableAnimation
|
||||
let isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation
|
||||
let isVisibleForAnimations = self.effectiveEnableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation
|
||||
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||
itemLayer.reloadAnimation()
|
||||
|
|
@ -410,7 +415,12 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||
public var spoilerColor: UIColor = .black
|
||||
public var balancedTextLayout: Bool = false
|
||||
|
||||
private var enableLooping: Bool = true
|
||||
public var enableLooping: Bool = true
|
||||
public var energySavingEnableLooping: Bool = true
|
||||
|
||||
private var effectiveEnableLooping: Bool {
|
||||
return self.enableLooping && self.energySavingEnableLooping
|
||||
}
|
||||
|
||||
public var arguments: TextNodeWithEntities.Arguments?
|
||||
|
||||
|
|
@ -423,7 +433,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||
didSet {
|
||||
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility {
|
||||
for (_, itemLayer) in self.inlineStickerItemLayers {
|
||||
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
|
||||
let isVisibleForAnimations = self.effectiveEnableLooping && self.visibility && itemLayer.enableAnimation
|
||||
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||
itemLayer.isVisibleForAnimations = isVisibleForAnimations
|
||||
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||
|
|
@ -575,7 +585,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||
}
|
||||
|
||||
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, fontSizeNorm: CGFloat) {
|
||||
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||
self.energySavingEnableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||
|
||||
var nextIndexById: [Int64: Int] = [:]
|
||||
var validIds: [InlineStickerItemLayer.Key] = []
|
||||
|
|
@ -613,7 +623,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||
}
|
||||
|
||||
itemLayer.enableAnimation = stickerItem.enableAnimation
|
||||
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
|
||||
let isVisibleForAnimations = self.effectiveEnableLooping && self.visibility && itemLayer.enableAnimation
|
||||
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||
itemLayer.isVisibleForAnimations = isVisibleForAnimations
|
||||
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "backspace_24.svg",
|
||||
"filename" : "backspace_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.6581 5.56807C9.26054 4.9489 10.0877 4.59961 10.9516 4.59961H18.4004C20.1677 4.59961 21.6004 6.0323 21.6004 7.79961V16.1996C21.6004 17.9669 20.1677 19.3996 18.4004 19.3996H10.9516C10.0877 19.3996 9.26054 19.0503 8.6581 18.4311L3.486 13.1154C2.88168 12.4943 2.88168 11.5049 3.486 10.8838L8.6581 5.56807Z" stroke="black" stroke-width="1.66"/>
|
||||
<path d="M11.5996 9.2002L17.1996 14.8002" stroke="black" stroke-width="1.66" stroke-linecap="round"/>
|
||||
<path d="M11.5996 14.7998L17.1996 9.1998" stroke="black" stroke-width="1.66" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 659 B |
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "keyboard_24.svg",
|
||||
"filename" : "keyboard_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="9.2" stroke="black" stroke-width="1.66"/>
|
||||
<ellipse cx="12" cy="12" rx="5.2" ry="9.2" stroke="black" stroke-width="1.328"/>
|
||||
<path d="M12 2.8V21.2" stroke="black" stroke-width="1.328"/>
|
||||
<path d="M2.8 12L21.2 12" stroke="black" stroke-width="1.328"/>
|
||||
<path d="M4.4 17.6C4.4 17.6 6.75325 16 12 16C17.2468 16 19.6 17.6 19.6 17.6" stroke="black" stroke-width="1.328"/>
|
||||
<path d="M19.6 6.4C19.6 6.4 17.2468 8 12 8C6.75325 8 4.4 6.4 4.4 6.4" stroke="black" stroke-width="1.328"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 601 B |
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_30.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "keyboard_2444.svg",
|
||||
"filename" : "settings_30 (4).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0626 21.9306H12.9374C13.652 21.9306 14.1996 21.494 14.3666 20.8065L14.7657 19.0693L15.0627 18.9672L16.5755 19.8961C17.1787 20.277 17.8748 20.1841 18.3852 19.6732L19.6846 18.3819C20.195 17.871 20.2879 17.165 19.9073 16.5704L18.9607 15.0656L19.0721 14.7868L20.8076 14.3781C21.4851 14.2109 21.9306 13.6535 21.9306 12.9475V11.1082C21.9306 10.4022 21.4944 9.84484 20.8076 9.67761L19.0906 9.2596L18.9699 8.96231L19.9166 7.45739C20.2971 6.86287 20.2043 6.16613 19.6939 5.64595L18.3945 4.34539C17.8934 3.84375 17.1973 3.75086 16.594 4.12244L15.0812 5.05138L14.7657 4.93066L14.3666 3.19348C14.1996 2.50605 13.652 2.06944 12.9374 2.06944H11.0626C10.348 2.06944 9.80046 2.50605 9.63335 3.19348L9.22503 4.93066L8.90949 5.05138L7.40598 4.12244C6.80272 3.75086 6.09735 3.84375 5.5962 4.34539L4.30614 5.64595C3.79569 6.16613 3.6936 6.86287 4.0834 7.45739L5.02077 8.96231L4.90939 9.2596L3.19243 9.67761C2.50565 9.84484 2.06945 10.4022 2.06945 11.1082V12.9475C2.06945 13.6535 2.51493 14.2109 3.19243 14.3781L4.92794 14.7868L5.03005 15.0656L4.09268 16.5704C3.70289 17.165 3.80498 17.871 4.31542 18.3819L5.60548 19.6732C6.1159 20.1841 6.82127 20.277 7.42453 19.8961L8.92804 18.9672L9.22503 19.0693L9.63335 20.8065C9.80046 21.494 10.348 21.9306 11.0626 21.9306ZM11.2111 20.4814C11.0534 20.4814 10.9698 20.4164 10.942 20.2677L10.3851 17.9639C9.81901 17.8246 9.28997 17.6016 8.89093 17.3508L6.86766 18.5956C6.75627 18.6792 6.63567 18.6606 6.52428 18.5492L5.42915 17.453C5.32704 17.3508 5.31776 17.2393 5.39198 17.1092L6.63567 15.1027C6.42217 14.7126 6.1809 14.1831 6.03241 13.6164L3.73073 13.0683C3.58223 13.0404 3.51727 12.9568 3.51727 12.7989V11.2475C3.51727 11.0803 3.57295 11.006 3.73073 10.9781L6.02313 10.4208C6.17163 9.81694 6.45 9.26888 6.61711 8.92515L5.3827 6.91859C5.29921 6.77926 5.30849 6.66775 5.41059 6.55631L6.515 5.47873C6.62639 5.36722 6.72844 5.34867 6.86766 5.43228L8.87232 6.6492C9.27142 6.42625 9.83757 6.19402 10.3944 6.03607L10.942 3.73228C10.9698 3.58365 11.0534 3.51862 11.2111 3.51862H12.7889C12.9466 3.51862 13.0302 3.58365 13.0487 3.73228L13.6149 6.05469C14.1903 6.2033 14.6915 6.43553 15.1091 6.65848L17.1231 5.43228C17.2716 5.34867 17.3643 5.36722 17.485 5.47873L18.5801 6.55631C18.6915 6.66775 18.6915 6.77926 18.608 6.91859L17.3737 8.92515C17.55 9.26888 17.8191 9.81694 17.9676 10.4208L20.2693 10.9781C20.4178 11.006 20.4827 11.0803 20.4827 11.2475V12.7989C20.4827 12.9568 20.4085 13.0404 20.2693 13.0683L17.9583 13.6164C17.8098 14.1831 17.5778 14.7126 17.3551 15.1027L18.5987 17.1092C18.673 17.2393 18.673 17.3508 18.5616 17.453L17.4757 18.5492C17.3551 18.6606 17.2437 18.6792 17.1231 18.5956L15.0998 17.3508C14.7007 17.6016 14.181 17.8246 13.6149 17.9639L13.0487 20.2677C13.0302 20.4164 12.9466 20.4814 12.7889 20.4814H11.2111ZM12 15.5486C13.9397 15.5486 15.536 13.9508 15.536 12C15.536 10.0678 13.9397 8.46997 12 8.46997C10.0603 8.46997 8.45472 10.0678 8.45472 12C8.45472 13.9415 10.051 15.5486 12 15.5486ZM12 14.1087C10.8491 14.1087 9.90251 13.1612 9.90251 12C9.90251 10.8574 10.8491 9.90984 12 9.90984C13.1323 9.90984 14.0789 10.8574 14.0789 12C14.0789 13.1519 13.1323 14.1087 12 14.1087Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Group 1.svg",
|
||||
"filename" : "trending_44.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 4.515C12.3433 4.515 4.515 12.3433 4.515 22C4.515 31.6567 12.3433 39.485 22 39.485C31.6567 39.485 39.485 31.6567 39.485 22C39.485 12.3433 31.6567 4.515 22 4.515ZM1.485 22C1.485 10.6699 10.6699 1.485 22 1.485C33.3301 1.485 42.515 10.6699 42.515 22C42.515 33.3301 33.3301 42.515 22 42.515C10.6699 42.515 1.485 33.3301 1.485 22ZM21.9986 13.152C22.8353 13.152 23.5136 13.8303 23.5136 14.667V20.4854H29.3319C30.1686 20.4854 30.8469 21.1636 30.8469 22.0004C30.8469 22.8371 30.1686 23.5154 29.3319 23.5154H23.5136V29.3337C23.5136 30.1704 22.8353 30.8487 21.9986 30.8487C21.1619 30.8487 20.4836 30.1704 20.4836 29.3337V23.5154H14.6652C13.8285 23.5154 13.1502 22.8371 13.1502 22.0004C13.1502 21.1636 13.8285 20.4854 14.6652 20.4854H20.4836V14.667C20.4836 13.8303 21.1619 13.152 21.9986 13.152Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 956 B |
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/trending_44.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/trending_44.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearing.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearing.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wear_30 (2).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearing.imageset/wear_30 (2).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearing.imageset/wear_30 (2).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "dice.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/dice.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/dice.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -8054,64 +8054,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
self.presentEmojiList(references: [stickerPackReference], previewIconFile: previewIconFile)
|
||||
}
|
||||
}
|
||||
|
||||
func displayDiceTooltip(dice: TelegramMediaDice) {
|
||||
guard let _ = dice.value else {
|
||||
return
|
||||
}
|
||||
self.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
let value: String?
|
||||
let emoji = dice.emoji.strippedEmoji
|
||||
switch emoji {
|
||||
case "🎲":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3B2
|
||||
case "🎯":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3AF
|
||||
case "🏀":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3C0
|
||||
case "⚽":
|
||||
value = self.presentationData.strings.Conversation_Dice_u26BD
|
||||
case "🎰":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3B0
|
||||
case "🎳":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3B3
|
||||
default:
|
||||
let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
|
||||
let key = "Conversation.Dice.u\(emojiHex)"
|
||||
if let string = self.presentationData.strings.primaryComponent.dict[key] {
|
||||
value = string
|
||||
} else if let string = self.presentationData.strings.secondaryComponent?.dict[key] {
|
||||
value = string
|
||||
} else {
|
||||
value = nil
|
||||
}
|
||||
}
|
||||
if let value = value {
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessagesToChat(self.presentationInterfaceState) ? self.presentationData.strings.Conversation_SendDice : nil), elevatedLayout: false, action: { [weak self] action in
|
||||
if let self, canSendMessagesToChat(self.presentationInterfaceState), action == .undo {
|
||||
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
}
|
||||
|
||||
func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false) -> [EnqueueMessage] {
|
||||
var defaultThreadId: Int64?
|
||||
var defaultReplyMessageSubject: EngineMessageReplySubject?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
import Foundation
|
||||
import AccountContext
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
import EmojiGameStakeScreen
|
||||
import ChatPresentationInterfaceState
|
||||
import TelegramStringFormatting
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func presentEmojiGameStake() {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.EmojiGame())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] gameInfo in
|
||||
guard let self, case let .available(info) = gameInfo else {
|
||||
return
|
||||
}
|
||||
let controller = EmojiGameStakeScreen(
|
||||
context: self.context,
|
||||
gameInfo: info,
|
||||
completion: { [weak self] stake in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: "🎲", tonAmount: stake.value > 0 ? stake.value : nil)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
|
||||
})
|
||||
}
|
||||
)
|
||||
self.push(controller)
|
||||
})
|
||||
}
|
||||
|
||||
func displayDiceTooltip(dice: TelegramMediaDice) {
|
||||
guard let _ = dice.value else {
|
||||
return
|
||||
}
|
||||
self.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
let emoji = dice.emoji.strippedEmoji
|
||||
|
||||
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.EmojiGame())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] gameInfo in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let canSendMessages = canSendMessagesToChat(self.presentationInterfaceState)
|
||||
let value: String?
|
||||
var changeAction: String?
|
||||
var tonAmount: Int64?
|
||||
if canSendMessages, emoji == "🎲", case let .available(info) = gameInfo {
|
||||
//TODO:localize
|
||||
let currentStake = info.previousStake
|
||||
value = "Stake: $ \(formatTonAmountText(currentStake, dateTimeFormat: self.presentationData.dateTimeFormat))"
|
||||
changeAction = "change"
|
||||
tonAmount = info.previousStake
|
||||
} else {
|
||||
switch emoji {
|
||||
case "🎲":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3B2
|
||||
case "🎯":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3AF
|
||||
case "🏀":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3C0
|
||||
case "⚽":
|
||||
value = self.presentationData.strings.Conversation_Dice_u26BD
|
||||
case "🎰":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3B0
|
||||
case "🎳":
|
||||
value = self.presentationData.strings.Conversation_Dice_u1F3B3
|
||||
default:
|
||||
let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
|
||||
let key = "Conversation.Dice.u\(emojiHex)"
|
||||
if let string = self.presentationData.strings.primaryComponent.dict[key] {
|
||||
value = string
|
||||
} else if let string = self.presentationData.strings.secondaryComponent?.dict[key] {
|
||||
value = string
|
||||
} else {
|
||||
value = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if let value = value {
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessages ? self.presentationData.strings.Conversation_SendDice : nil, changeAction: changeAction), elevatedLayout: false, action: { [weak self] action in
|
||||
if let self, canSendMessagesToChat(self.presentationInterfaceState) {
|
||||
switch action {
|
||||
case .undo:
|
||||
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji, tonAmount: tonAmount)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
|
||||
})
|
||||
case .info:
|
||||
if let _ = changeAction {
|
||||
self.presentEmojiGameStake()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ private enum CreateChannelEntry: ItemListNodeEntry {
|
|||
let arguments = arguments as! CreateChannelArguments
|
||||
switch self {
|
||||
case let .channelInfo(_, _, dateTimeFormat, peer, state, avatar):
|
||||
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
||||
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
||||
arguments.updateEditingName(editingName)
|
||||
}, editingNameCompleted: {
|
||||
arguments.focusOnDescription()
|
||||
|
|
@ -202,11 +202,11 @@ private enum CreateChannelEntry: ItemListNodeEntry {
|
|||
arguments.changeProfilePhoto()
|
||||
}, updatingImage: avatar, tag: CreateChannelEntryTag.info)
|
||||
case let .setProfilePhoto(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.changeProfilePhoto()
|
||||
})
|
||||
case let .descriptionSetup(_, text, value):
|
||||
return ItemListMultilineInputItem(presentationData: presentationData, text: value, placeholder: text, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in
|
||||
return ItemListMultilineInputItem(presentationData: presentationData, systemStyle: .glass, text: value, placeholder: text, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in
|
||||
arguments.updateEditingDescriptionText(updatedText)
|
||||
}, tag: CreateChannelEntryTag.description)
|
||||
case let .descriptionInfo(_, text):
|
||||
|
|
@ -214,7 +214,7 @@ private enum CreateChannelEntry: ItemListNodeEntry {
|
|||
case let .usernameHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .username(theme, placeholder, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updatePublicLinkText(updatedText)
|
||||
}, action: {
|
||||
})
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||
let arguments = arguments as! CreateGroupArguments
|
||||
switch self {
|
||||
case let .groupInfo(_, _, dateTimeFormat, peer, state, avatar):
|
||||
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
||||
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
||||
arguments.updateEditingName(editingName)
|
||||
}, editingNameCompleted: {
|
||||
arguments.done()
|
||||
|
|
@ -329,13 +329,13 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||
arguments.changeProfilePhoto()
|
||||
}, updatingImage: avatar, tag: CreateGroupEntryTag.info)
|
||||
case let .setProfilePhoto(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.changeProfilePhoto()
|
||||
})
|
||||
case let .usernameHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .username(theme, placeholder, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updatePublicLinkText(updatedText)
|
||||
}, action: {
|
||||
})
|
||||
|
|
@ -364,24 +364,24 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||
case let .usernameInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .topics(_, text):
|
||||
return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in })
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in })
|
||||
case let .topicsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .autoDelete(text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||
arguments.updateAutoDelete()
|
||||
}, tag: CreateGroupEntryTag.autoDelete)
|
||||
case let .autoDeleteInfo(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .member(_, _, _, dateTimeFormat, nameDisplayOrder, peer, presence):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
case let .locationHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .location(theme, location):
|
||||
let imageSignal = chatMapSnapshotImage(engine: arguments.context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
|
||||
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
|
||||
return ItemListAddressItem(theme: theme, systemStyle: .glass, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
|
||||
case let .changeLocation(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.changeLocation()
|
||||
})
|
||||
case let .locationInfo(_, text):
|
||||
|
|
@ -389,7 +389,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||
case let .venueHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .venue(_, _, venue):
|
||||
return ItemListVenueItem(presentationData: presentationData, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListVenueItem(presentationData: presentationData, systemStyle: .glass, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.updateWithVenue(venue)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -770,6 +770,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||
var startApp: String?
|
||||
var text: String?
|
||||
var profile: Bool = false
|
||||
var direct: Bool = false
|
||||
var referrer: String?
|
||||
var albumId: Int64?
|
||||
var collectionId: Int64?
|
||||
|
|
@ -823,6 +824,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||
startChannel = ""
|
||||
} else if queryItem.name == "profile" {
|
||||
profile = true
|
||||
} else if queryItem.name == "direct" {
|
||||
direct = true
|
||||
} else if queryItem.name == "startapp" {
|
||||
startApp = ""
|
||||
}
|
||||
|
|
@ -918,6 +921,13 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||
convertedUrl = current + "?profile"
|
||||
}
|
||||
}
|
||||
if direct, let current = convertedUrl {
|
||||
if current.contains("?") {
|
||||
convertedUrl = current + "&direct"
|
||||
} else {
|
||||
convertedUrl = current + "?direct"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if parsedUrl.host == "hostOverride" {
|
||||
if let components = URLComponents(string: "/?" + query) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public enum UndoOverlayContent {
|
|||
case swipeToReply(title: String, text: String)
|
||||
case actionSucceeded(title: String?, text: String, cancel: String?, destructive: Bool)
|
||||
case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext)
|
||||
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?)
|
||||
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?, changeAction: String?)
|
||||
case chatAddedToFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
|
||||
case chatRemovedFromFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
|
||||
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let undoButtonTextNode: ImmediateTextNode
|
||||
private let undoButtonNode: HighlightTrackingButtonNode
|
||||
|
||||
private let changeButtonBackground: ASImageNode
|
||||
private let changeButtonTextNode: ImmediateTextNode
|
||||
|
||||
private let panelNode: ASDisplayNode
|
||||
private let panelWrapperNode: ASDisplayNode
|
||||
private let action: (UndoOverlayAction) -> Bool
|
||||
|
|
@ -103,6 +107,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.changeButtonBackground = ASImageNode()
|
||||
self.changeButtonBackground.displaysAsynchronously = false
|
||||
self.changeButtonBackground.isUserInteractionEnabled = false
|
||||
|
||||
self.changeButtonTextNode = ImmediateTextNode()
|
||||
self.changeButtonTextNode.displaysAsynchronously = false
|
||||
self.changeButtonTextNode.isUserInteractionEnabled = false
|
||||
|
||||
var displayUndo = true
|
||||
var undoText = presentationData.strings.Undo_Undo
|
||||
var undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
|
||||
|
|
@ -624,7 +636,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: isVideo), width: 80, height: 80, mode: .direct(cachePathPrefix: nil))
|
||||
}
|
||||
}
|
||||
case let .dice(dice, context, text, action):
|
||||
case let .dice(dice, context, text, action, changeAction):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
|
|
@ -633,7 +645,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedText.string.range(of: "$"), let icon = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonMedium"), color: .white) {
|
||||
attributedText.addAttribute(.attachment, value: icon, range: NSRange(range, in: attributedText.string))
|
||||
attributedText.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedText.string))
|
||||
}
|
||||
self.textNode.attributedText = attributedText
|
||||
if let action = action {
|
||||
displayUndo = true
|
||||
|
|
@ -651,6 +667,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let changeAction {
|
||||
self.changeButtonTextNode.attributedText = NSAttributedString(string: changeAction, font: Font.regular(12.0), textColor: undoTextColor)
|
||||
self.changeButtonBackground.image = generateStretchableFilledCircleImage(diameter: 18.0, color: undoTextColor.withMultipliedAlpha(0.1))
|
||||
}
|
||||
|
||||
if dice.emoji == "🎰" {
|
||||
let slotMachineNode = SlotMachineAnimationNode(account: context.account, size: CGSize(width: 42.0, height: 42.0))
|
||||
|
|
@ -670,8 +691,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
switch stickerPack {
|
||||
case let .result(_, items, _):
|
||||
let item = items[Int(value)]
|
||||
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file._parse().resource), width: 120, height: 120, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file._parse().resource), width: 120, height: 120, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -1553,7 +1573,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
self.panelNode.backgroundColor = .clear
|
||||
}
|
||||
self.panelNode.clipsToBounds = true
|
||||
self.panelNode.cornerRadius = 14.0
|
||||
self.panelNode.cornerRadius = 25.0
|
||||
|
||||
self.panelWrapperNode = ASDisplayNode()
|
||||
|
||||
|
|
@ -1625,6 +1645,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
self.panelWrapperNode.addSubnode(self.undoButtonTextNode)
|
||||
self.panelWrapperNode.addSubnode(self.undoButtonNode)
|
||||
}
|
||||
if self.changeButtonBackground.image != nil {
|
||||
self.panelWrapperNode.addSubnode(self.changeButtonBackground)
|
||||
self.panelWrapperNode.addSubnode(self.changeButtonTextNode)
|
||||
}
|
||||
self.addSubnode(self.panelNode)
|
||||
self.addSubnode(self.panelWrapperNode)
|
||||
|
||||
|
|
@ -1962,7 +1986,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
}
|
||||
contentHeight += textSize.height
|
||||
|
||||
contentHeight = max(49.0, contentHeight)
|
||||
contentHeight = max(50.0, contentHeight)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
switch self.placementPosition {
|
||||
|
|
@ -2024,6 +2048,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
}
|
||||
|
||||
if self.changeButtonTextNode.supernode != nil {
|
||||
let changeButtonTextSize = self.changeButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
let changeButtonFrame = CGRect(origin: CGPoint(x: textFrame.maxX + 10.0, y: floorToScreenPixels(textFrame.midY - changeButtonTextSize.height * 0.5)), size: changeButtonTextSize)
|
||||
transition.updateFrame(node: self.changeButtonTextNode, frame: changeButtonFrame)
|
||||
transition.updateFrame(node: self.changeButtonBackground, frame: changeButtonFrame.insetBy(dx: -6.0, dy: -2.0))
|
||||
}
|
||||
|
||||
if let iconNode = self.iconNode {
|
||||
let iconSize: CGSize
|
||||
if let size = self.iconImageSize {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public enum ParsedInternalPeerUrlParameter {
|
|||
case boost
|
||||
case text(String)
|
||||
case profile
|
||||
case direct
|
||||
case referrer(String)
|
||||
case storyFolder(Int64)
|
||||
case giftCollection(Int64)
|
||||
|
|
@ -393,6 +394,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
|
|||
return .peer(.name(peerName), .boost)
|
||||
} else if queryItem.name == "profile" {
|
||||
return .peer(.name(peerName), .profile)
|
||||
} else if queryItem.name == "direct" {
|
||||
return .peer(.name(peerName), .direct)
|
||||
} else if queryItem.name == "startapp" {
|
||||
var mode: ResolvedStartAppMode = .generic
|
||||
if let queryItems = components.queryItems {
|
||||
|
|
@ -836,6 +839,26 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||
switch parameter {
|
||||
case .profile:
|
||||
return .single(.result(.peer(peer._asPeer(), .info(nil))))
|
||||
case .direct:
|
||||
if case let .channel(channel) = peer, let monoforumId = channel.linkedMonoforumId {
|
||||
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: monoforumId))
|
||||
|> mapToSignal { peer -> Signal<EnginePeer?, NoError> in
|
||||
if let peer {
|
||||
return .single(peer)
|
||||
} else {
|
||||
return context.engine.peers.findChannelById(channelId: monoforumId.id._internalGetInt64Value())
|
||||
}
|
||||
}
|
||||
|> map { peer -> ResolveInternalUrlResult in
|
||||
if let peer {
|
||||
return .result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))
|
||||
} else {
|
||||
return .result(.peer(nil, .info(nil)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.result(.peer(nil, .info(nil))))
|
||||
}
|
||||
case let .text(text):
|
||||
var textInputState: ChatTextInputState?
|
||||
if !text.isEmpty {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue