diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index 9214a41be6..88b6735a03 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -114,7 +114,7 @@ public final class SheetComponent: Component { private let scrollView: ScrollView private let backgroundView: UIView private var effectView: UIVisualEffectView? - private let contentView: ComponentHostView + private let contentView: ComponentView private var isAnimatingOut: Bool = false private var previousIsDisplaying: Bool = false @@ -139,7 +139,7 @@ public final class SheetComponent: Component { self.backgroundView.layer.cornerRadius = 12.0 self.backgroundView.layer.masksToBounds = true - self.contentView = ComponentHostView() + self.contentView = ComponentView() super.init(frame: frame) @@ -147,7 +147,6 @@ public final class SheetComponent: Component { self.addSubview(self.dimView) self.scrollView.addSubview(self.backgroundView) - self.scrollView.addSubview(self.contentView) self.addSubview(self.scrollView) self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimViewTapGesture(_:)))) @@ -251,18 +250,21 @@ public final class SheetComponent: Component { self.isUserInteractionEnabled = false self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + guard let contentView = self.contentView.view else { + return + } if let initialVelocity = initialVelocity { let transition = ContainedViewLayoutTransition.animated(duration: 0.35, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) let contentOffset = (self.scrollView.contentOffset.y + self.scrollView.contentInset.top - self.scrollView.contentSize.height) * -1.0 - let dismissalOffset = self.scrollView.contentSize.height + abs(self.contentView.frame.minY) + let dismissalOffset = self.scrollView.contentSize.height + abs(contentView.frame.minY) let delta = dismissalOffset - contentOffset transition.updatePosition(layer: self.scrollView.layer, position: CGPoint(x: self.scrollView.center.x, y: self.scrollView.center.y + delta), completion: { _ in completion() }) } else { - self.scrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.scrollView.contentSize.height + abs(self.contentView.frame.minY)), duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + self.scrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.scrollView.contentSize.height + abs(contentView.frame.minY)), duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in completion() }) } @@ -316,6 +318,7 @@ public final class SheetComponent: Component { containerSize = CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) } + self.contentView.parentState = state let contentSize = self.contentView.update( transition: transition, component: component.content, @@ -326,19 +329,23 @@ public final class SheetComponent: Component { ) self.ignoreScrolling = true - - if sheetEnvironment.isCentered { - let y: CGFloat = floorToScreenPixels((availableSize.height - contentSize.height) / 2.0) - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) - if let effectView = self.effectView { - transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) + if let contentView = self.contentView.view { + if contentView.superview == nil { + self.scrollView.addSubview(contentView) } - } else { - transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) - if let effectView = self.effectView { - transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + if sheetEnvironment.isCentered { + let y: CGFloat = floorToScreenPixels((availableSize.height - contentSize.height) / 2.0) + transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) + if let effectView = self.effectView { + transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) + } + } else { + transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + if let effectView = self.effectView { + transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + } } } transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) diff --git a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift index 64f3368a13..fe0338541c 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift @@ -23,12 +23,17 @@ public func PremiumBoostScreen( pushController: @escaping (ViewController) -> Void, dismissed: @escaping () -> Void ) { - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).startStandalone(next: { peer in - guard let peer, let status else { + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { peer, accountPeer in + guard let peer, let accountPeer, let status else { return } + let isPremium = accountPeer.isPremium + var myBoostCount: Int32 = 0 var availableBoosts: [MyBoostStatus.Boost] = [] var occupiedBoosts: [MyBoostStatus.Boost] = [] @@ -95,22 +100,36 @@ public func PremiumBoostScreen( dismissImpl?() pushController(replaceController) } else { - let controller = textAlertController( - sharedContext: context.sharedContext, - updatedPresentationData: nil, - title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle, - text: presentationData.strings.ChannelBoost_Error_PremiumNeededText, - actions: [ - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { - dismissImpl?() - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil) - pushController(controller) - }) - ], - parseMarkdown: true - ) - presentController(controller) + if isPremium { + let controller = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: "More Boosts Needed", + text: "To boost **\(peer.compactDisplayTitle)**, get more boosts by gifting **Telegram Premium** to a friend.", + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ], + parseMarkdown: true + ) + presentController(controller) + } else { + let controller = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle, + text: presentationData.strings.ChannelBoost_Error_PremiumNeededText, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { + dismissImpl?() + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil) + pushController(controller) + }) + ], + parseMarkdown: true + ) + presentController(controller) + } } } else { dismissImpl?() diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index f3d7c5bf3b..ca4d5b5f40 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -1159,7 +1159,7 @@ private final class LimitSheetContent: CombinedComponent { state.myBoostCount = myBoostCount boostUpdated = true } - useAlternateText = myBoostCount > 0 + useAlternateText = (myBoostCount % 2) != 0 iconName = "Premium/Boost" badgeText = "\(component.count)" @@ -1390,7 +1390,7 @@ private final class LimitSheetContent: CombinedComponent { ) var buttonOffset: CGFloat = 0.0 - var textOffset: CGFloat = 228.0 + topOffset + var textOffset: CGFloat = 184.0 + topOffset if case let .storiesChannelBoost(_, _, _, _, _, link, _) = component.subject { if let link { @@ -1416,13 +1416,11 @@ private final class LimitSheetContent: CombinedComponent { transition: context.transition ) buttonOffset += 66.0 - + let linkFrame = CGRect(origin: CGPoint(x: sideInset, y: textOffset + ceil((textChild?.size ?? .zero).height / 2.0) + 24.0), size: linkButton.size) context.add(linkButton .position(CGPoint(x: linkFrame.midX, y: linkFrame.midY)) ) - } else { - textOffset -= 26.0 } } if isPremiumDisabled { @@ -1436,6 +1434,9 @@ private final class LimitSheetContent: CombinedComponent { var textSize: CGSize if let textChild { textSize = textChild.size + + textOffset += textSize.height / 2.0 + context.add(textChild .position(CGPoint(x: context.availableSize.width / 2.0, y: textOffset)) .appear(Transition.Appear({ _, view, transition in @@ -1451,8 +1452,10 @@ private final class LimitSheetContent: CombinedComponent { })) ) } else if let alternateTextChild { - textOffset += 9.0 textSize = alternateTextChild.size + + textOffset += textSize.height / 2.0 + context.add(alternateTextChild .position(CGPoint(x: context.availableSize.width / 2.0, y: textOffset)) .appear(Transition.Appear({ _, view, transition in @@ -1559,7 +1562,7 @@ private final class LimitSheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.maxY + 50.0 + giftText.size.height / 2.0)) ) - additionalContentHeight += giftText.size.height + 30.0 + additionalContentHeight += giftText.size.height + 50.0 } contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + additionalContentHeight + 5.0 + environment.safeInsets.bottom) diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index a35783909f..9f27829137 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -27,13 +27,15 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { let context: AccountContext let peerId: EnginePeer.Id let myBoostStatus: MyBoostStatus + let initiallySelectedSlot: Int32? let selectedSlotsUpdated: ([Int32]) -> Void let presentController: (ViewController) -> Void - init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, selectedSlotsUpdated: @escaping ([Int32]) -> Void, presentController: @escaping (ViewController) -> Void) { + init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, initiallySelectedSlot: Int32?, selectedSlotsUpdated: @escaping ([Int32]) -> Void, presentController: @escaping (ViewController) -> Void) { self.context = context self.peerId = peerId self.myBoostStatus = myBoostStatus + self.initiallySelectedSlot = initiallySelectedSlot self.selectedSlotsUpdated = selectedSlotsUpdated self.presentController = presentController } @@ -62,13 +64,17 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { var cachedCloseImage: (UIImage, PresentationTheme)? - init(context: AccountContext, peerId: EnginePeer.Id) { + init(context: AccountContext, peerId: EnginePeer.Id, initiallySelectedSlot: Int32?) { self.context = context self.currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) super.init() + if let initiallySelectedSlot { + self.selectedSlots.append(initiallySelectedSlot) + } + self.disposable.set((context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).startStrict(next: { [weak self] peer in guard let self else { @@ -94,7 +100,7 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, peerId: self.peerId) + return State(context: self.context, peerId: self.peerId, initiallySelectedSlot: self.initiallySelectedSlot) } static var body: Body { @@ -180,6 +186,8 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { .position(CGPoint(x: availableSize.width / 2.0, y: 172.0)) ) + let hasSelection = occupiedBoosts.count > 1 + let selectedSlotsUpdated = context.component.selectedSlotsUpdated let presentController = context.component.presentController for i in 0 ..< occupiedBoosts.count { @@ -193,11 +201,11 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { if let cooldownUntil = boost.cooldownUntil, cooldownUntil > state.currentTime { let duration = cooldownUntil - state.currentTime let durationValue = stringForDuration(duration, position: nil) - subtitle = "available in \(durationValue)" + subtitle = "Available in \(durationValue)" isEnabled = false } else { let expiresValue = stringForDate(timestamp: boost.expires, strings: strings) - subtitle = "boost expires on \(expiresValue)" + subtitle = "Boost expires on \(expiresValue)" } let accountContext = context.component.context @@ -216,12 +224,12 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { subtitle: subtitle, subtitleAccessory: .none, presence: nil, - selectionState: .editing(isSelected: state.selectedSlots.contains(boost.slot), isTinted: false), + selectionState: hasSelection ? .editing(isSelected: state.selectedSlots.contains(boost.slot), isTinted: false) : .none, selectionPosition: .right, isEnabled: isEnabled, hasNext: i != occupiedBoosts.count - 1, action: { [weak state] _ in - guard let state else { + guard let state, hasSelection else { return } if isEnabled { @@ -433,6 +441,35 @@ public class ReplaceBoostScreen: ViewController { effectiveExpanded = true } + let environment = ViewControllerComponentContainer.Environment( + statusBarHeight: 0.0, + navigationHeight: navigationHeight, + safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), + inputHeight: layout.inputHeight ?? 0.0, + metrics: layout.metrics, + deviceMetrics: layout.deviceMetrics, + isVisible: self.currentIsVisible, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + controller: { [weak self] in + return self?.controller + } + ) + let contentSize = self.hostView.update( + transition: transition, + component: self.component, + environment: { + environment + }, + forceUpdate: true, + containerSize: CGSize(width: layout.size.width, height: 10000.0) + ) +// contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) + transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) + + self.scrollView.contentSize = contentSize + let isLandscape = layout.orientation == .landscape let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset let topInset: CGFloat @@ -499,36 +536,7 @@ public class ReplaceBoostScreen: ViewController { transition.setFrame(view: self.containerView, frame: clipFrame) transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) - - let environment = ViewControllerComponentContainer.Environment( - statusBarHeight: 0.0, - navigationHeight: navigationHeight, - safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), - inputHeight: layout.inputHeight ?? 0.0, - metrics: layout.metrics, - deviceMetrics: layout.deviceMetrics, - isVisible: self.currentIsVisible, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - dateTimeFormat: self.presentationData.dateTimeFormat, - controller: { [weak self] in - return self?.controller - } - ) - var contentSize = self.hostView.update( - transition: transition, - component: self.component, - environment: { - environment - }, - forceUpdate: true, - containerSize: CGSize(width: clipFrame.size.width, height: 10000.0) - ) - contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) - transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) - - self.scrollView.contentSize = contentSize - + let footerInsets = UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right) transition.setFrame(view: self.footerView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topInset), size: layout.size)) @@ -564,7 +572,11 @@ public class ReplaceBoostScreen: ViewController { if layout.size.width <= 320.0 { factor = 0.15 } - return floor(max(layout.size.width, layout.size.height) * factor) + if self.scrollView.contentSize.height > 0.0 && self.scrollView.contentSize.height < layout.size.height / 2.0 { + return layout.size.height - self.scrollView.contentSize.height - layout.intrinsicInsets.bottom - 154.0 + } else { + return floor(max(layout.size.width, layout.size.height) * factor) + } } else { return 210.0 } @@ -590,7 +602,7 @@ public class ReplaceBoostScreen: ViewController { } let isLandscape = layout.orientation == .landscape - let edgeTopInset = isLandscape ? 0.0 : defaultTopInset + let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset switch recognizer.state { case .began: @@ -783,15 +795,23 @@ public class ReplaceBoostScreen: ViewController { public convenience init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, replaceBoosts: @escaping ([Int32]) -> Void) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var initiallySelectedSlot: Int32? + let occupiedBoosts = myBoostStatus.boosts.filter { $0.peer?.id != peerId && $0.peer != nil }.sorted { lhs, rhs in + return lhs.date < rhs.date + } + if occupiedBoosts.count == 1, let boost = occupiedBoosts.first { + initiallySelectedSlot = boost.slot + } + var selectedSlotsUpdatedImpl: (([Int32]) -> Void)? var presentControllerImpl: ((ViewController) -> Void)? - self.init(context: context, component: ReplaceBoostScreenComponent(context: context, peerId: peerId, myBoostStatus: myBoostStatus, selectedSlotsUpdated: { slots in + self.init(context: context, component: ReplaceBoostScreenComponent(context: context, peerId: peerId, myBoostStatus: myBoostStatus, initiallySelectedSlot: initiallySelectedSlot, selectedSlotsUpdated: { slots in selectedSlotsUpdatedImpl?(slots) }, presentController: { c in presentControllerImpl?(c) })) - self.title = "Reassign Boost" + self.title = "Reassign Boosts" self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) @@ -806,6 +826,10 @@ public class ReplaceBoostScreen: ViewController { } self.replaceBoosts = replaceBoosts + + if let initiallySelectedSlot { + self.node.selectedSlots = [initiallySelectedSlot] + } } private init(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment { @@ -989,6 +1013,9 @@ private final class FooterView: UIView { self.addSubview(view) } view.frame = CGRect(origin: CGPoint(x: insets.left + buttonInset, y: panelFrame.minY + inset), size: buttonSize) + + buttonTransition.setAlpha(view: view, alpha: count > 0 ? 1.0 : 0.3) + view.isUserInteractionEnabled = count > 0 } self.backgroundNode.frame = panelFrame diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 3918ba3be1..f11967ed15 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -978,13 +978,28 @@ public final class WebAppController: ViewController, AttachmentContainable { } case "web_app_read_text_from_clipboard": if let json = json, let requestId = json["req_id"] as? String { - let currentTimestamp = CACurrentMediaTime() - var fillData = false - if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0, self.controller?.url == nil { - self.webView?.lastTouchTimestamp = nil - fillData = true - } - self.sendClipboardTextEvent(requestId: requestId, fillData: fillData) + let botId = controller.botId + let isAttachMenu = controller.url == nil + + let _ = (self.context.engine.messages.attachMenuBots() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] attachMenuBots in + guard let self else { + return + } + let currentTimestamp = CACurrentMediaTime() + var fillData = false + + let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) }) + if isAttachMenu || attachMenuBot != nil { + if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 { + self.webView?.lastTouchTimestamp = nil + fillData = true + } + } + + self.sendClipboardTextEvent(requestId: requestId, fillData: fillData) + }) } case "web_app_request_write_access": self.requestWriteAccess() @@ -1499,16 +1514,6 @@ public final class WebAppController: ViewController, AttachmentContainable { let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) }) -// if url == nil { -// if forceHasSettings { -// hasSettings = true -// } else { -// hasSettings = attachMenuBot?.flags.contains(.hasSettings) == true -// } -// } else { -// hasSettings = forceHasSettings -// } - if hasSettings { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)