Various improvements

This commit is contained in:
Ilya Laktyushin 2025-12-16 20:05:44 +04:00
parent f781dbd25f
commit 28d4e1e171
76 changed files with 4396 additions and 620 deletions

View file

@ -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";

View file

@ -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",

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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()
}

View file

@ -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()
})
}

View file

@ -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)

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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))
}

View file

@ -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
}
}
}

View file

@ -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": [],

View file

@ -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",

View file

@ -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))

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -32,6 +32,7 @@ swift_library(
"//submodules/WallpaperBackgroundNode",
"//submodules/LocalMediaResources",
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/ChatPresentationInterfaceState",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TelegramUI/Components/ChatControllerInteraction",

View file

@ -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 {

View file

@ -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 {

View file

@ -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) {

View file

@ -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))

View 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",
],
)

View file

@ -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",

View file

@ -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)

View file

@ -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)

View file

@ -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,

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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

View file

@ -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)
}

View file

@ -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",
],
)

View file

@ -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)
}
}

View 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",
],
)

View file

@ -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)
}
}
}

View file

@ -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 {

View file

@ -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",

View file

@ -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)))
})
}

View file

@ -23,6 +23,8 @@ swift_library(
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramCore",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
],
visibility = [
"//visibility:public",

View file

@ -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
)),

View file

@ -23,6 +23,7 @@ swift_library(
"//submodules/TelegramUI/Components/LiquidLens",
"//submodules/AppBundle",
"//submodules/SearchBarNode",
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
],
visibility = [
"//visibility:public",

View file

@ -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

View file

@ -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",
],
)

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "backspace_24.svg",
"filename" : "backspace_30.pdf",
"idiom" : "universal"
}
],

View file

@ -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

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "keyboard_24.svg",
"filename" : "keyboard_30.pdf",
"idiom" : "universal"
}
],

View file

@ -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

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "keyboard_2444.svg",
"filename" : "settings_30 (4).pdf",
"idiom" : "universal"
}
],

View file

@ -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

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "Group 1.svg",
"filename" : "trending_44.pdf",
"idiom" : "universal"
}
],

View file

@ -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

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "wear_30 (2).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "dice.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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?

View file

@ -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)
}
})
}
}

View file

@ -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: {
})

View file

@ -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)
})
}

View file

@ -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) {

View file

@ -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)

View file

@ -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 {

View file

@ -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 {