Poll UI improvements

This commit is contained in:
Ali 2020-01-12 22:07:37 +04:00
parent 0e792f0502
commit 2a6f1f811b
13 changed files with 196 additions and 59 deletions

View File

@ -197,6 +197,7 @@ private enum CreatePollEntryId: Hashable {
private enum CreatePollEntryTag: Equatable, ItemListItemTag {
case text
case option(Int)
case optionsInfo
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? CreatePollEntryTag {
@ -333,7 +334,7 @@ private enum CreatePollEntry: ItemListNodeEntry {
arguments.toggleOptionSelected(id)
}, tag: CreatePollEntryTag.option(id))
case let .optionsInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section, tag: CreatePollEntryTag.optionsInfo)
case let .anonymousVotes(text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateAnonymous(value)
@ -421,6 +422,7 @@ public func createPollController(context: AccountContext, peer: Peer, completion
var ensureTextVisibleImpl: (() -> Void)?
var ensureOptionVisibleImpl: ((Int) -> Void)?
var displayQuizTooltipImpl: ((Bool) -> Void)?
var attemptNavigationImpl: (() -> Bool)?
let actionsDisposable = DisposableSet()
@ -714,20 +716,7 @@ public func createPollController(context: AccountContext, peer: Peer, completion
})
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
let state = stateValue.with { $0 }
var hasNonEmptyOptions = false
for i in 0 ..< state.options.count {
let optionText = state.options[i].item.text.trimmingCharacters(in: .whitespacesAndNewlines)
if !optionText.isEmpty {
hasNonEmptyOptions = true
}
}
if hasNonEmptyOptions || !state.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.CreatePoll_CancelConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
dismissImpl?()
})]), nil)
} else {
if let attemptNavigationImpl = attemptNavigationImpl, attemptNavigationImpl() {
dismissImpl?()
}
})
@ -797,14 +786,23 @@ public func createPollController(context: AccountContext, peer: Peer, completion
var resultItemNode: ListViewItemNode?
let state = stateValue.with({ $0 })
var isLast = false
if state.options.last?.item.id == id {
isLast = true
}
if resultItemNode == nil {
let _ = controller.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListItemNode {
if let tag = itemNode.tag, tag.isEqual(to: CreatePollEntryTag.option(id)) {
resultItemNode = itemNode as? ListViewItemNode
return true
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag {
if isLast {
if tag.isEqual(to: CreatePollEntryTag.optionsInfo) {
resultItemNode = itemNode as? ListViewItemNode
return true
}
} else {
if tag.isEqual(to: CreatePollEntryTag.option(id)) {
resultItemNode = itemNode as? ListViewItemNode
return true
}
}
}
return false
@ -850,10 +848,10 @@ public func createPollController(context: AccountContext, peer: Peer, completion
}
}
}
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [CreatePollEntry]) -> Void in
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [CreatePollEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .option(option) = fromEntry else {
return
return .single(false)
}
let id = option.id
var referenceId: Int?
@ -873,13 +871,18 @@ public func createPollController(context: AccountContext, peer: Peer, completion
} else {
afterAll = true
}
var didReorder = false
updateState { state in
var state = state
var options = state.options
var reorderOption: OrderedLinkedListItem<CreatePollControllerOption>?
var previousIndex: Int?
for i in 0 ..< options.count {
if options[i].item.id == id {
reorderOption = options[i]
previousIndex = i
options.remove(at: i)
break
}
@ -890,8 +893,10 @@ public func createPollController(context: AccountContext, peer: Peer, completion
for i in 0 ..< options.count - 1 {
if options[i].item.id == referenceId {
if fromIndex < toIndex {
didReorder = previousIndex != i + 1
options.insert(reorderOption.item, at: i + 1, id: reorderOption.ordering.id)
} else {
didReorder = previousIndex != i
options.insert(reorderOption.item, at: i, id: reorderOption.ordering.id)
}
inserted = true
@ -900,17 +905,22 @@ public func createPollController(context: AccountContext, peer: Peer, completion
}
if !inserted {
if options.count >= 2 {
didReorder = previousIndex != options.count - 1
options.insert(reorderOption.item, at: options.count - 1, id: reorderOption.ordering.id)
} else {
didReorder = previousIndex != options.count
options.append(reorderOption.item, id: reorderOption.ordering.id)
}
}
} else if beforeAll {
didReorder = previousIndex != 0
options.insert(reorderOption.item, at: 0, id: reorderOption.ordering.id)
} else if afterAll {
if options.count >= 2 {
didReorder = previousIndex != options.count - 1
options.insert(reorderOption.item, at: options.count - 1, id: reorderOption.ordering.id)
} else {
didReorder = previousIndex != options.count
options.append(reorderOption.item, id: reorderOption.ordering.id)
}
}
@ -918,7 +928,34 @@ public func createPollController(context: AccountContext, peer: Peer, completion
}
return state
}
return .single(didReorder)
})
attemptNavigationImpl = {
let state = stateValue.with { $0 }
var hasNonEmptyOptions = false
for i in 0 ..< state.options.count {
let optionText = state.options[i].item.text.trimmingCharacters(in: .whitespacesAndNewlines)
if !optionText.isEmpty {
hasNonEmptyOptions = true
}
}
if hasNonEmptyOptions || !state.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.CreatePoll_CancelConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
dismissImpl?()
})]), nil)
return false
} else {
return true
}
}
controller.attemptNavigation = { _ in
if let attemptNavigationImpl = attemptNavigationImpl, attemptNavigationImpl() {
return true
}
return false
}
controller.isOpaqueWhenInOverlay = true
controller.blocksBackgroundWhenInOverlay = true
controller.experimentalSnapScrollToItem = true

View File

@ -391,6 +391,8 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
}
let bottomStripeWasHidden = strongSelf.bottomStripeNode.isHidden
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
@ -405,6 +407,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
hasBottomCorners = true
@ -418,7 +421,17 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width - bottomStripeInset, height: separatorHeight))
let previousX = strongSelf.bottomStripeNode.frame.minX
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
if !bottomStripeWasHidden {
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
}
} else {
let previousX = strongSelf.bottomStripeNode.frame.minX
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: strongSelf.bottomStripeNode.frame.minY), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
if !bottomStripeWasHidden {
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
}
}
let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit, transition)

View File

@ -435,6 +435,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
private func endReordering() {
self.itemReorderingTimer?.invalidate()
self.itemReorderingTimer = nil
self.lastReorderingOffset = nil
let f: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
@ -467,11 +471,32 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
private func checkItemReordering() {
if let reorderNode = self.reorderNode, let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
guard let verticalTopOffset = reorderNode.currentOffset() else {
return
}
private var itemReorderingTimer: SwiftSignalKit.Timer?
private var lastReorderingOffset: CGFloat?
private func checkItemReordering(force: Bool = false) {
guard let reorderNode = self.reorderNode, let verticalTopOffset = reorderNode.currentOffset() else {
return
}
if let lastReorderingOffset = self.lastReorderingOffset, abs(lastReorderingOffset - verticalTopOffset) < 4.0 && !force {
return
}
self.itemReorderingTimer?.invalidate()
self.itemReorderingTimer = nil
self.lastReorderingOffset = verticalTopOffset
if !force {
self.itemReorderingTimer = SwiftSignalKit.Timer(timeout: 0.025, repeat: false, completion: { [weak self] in
self?.checkItemReordering(force: true)
}, queue: Queue.mainQueue())
self.itemReorderingTimer?.start()
return
}
if let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
let verticalOffset = verticalTopOffset
var closestIndex: (Int, CGFloat)?
for i in 0 ..< self.itemNodes.count {

View File

@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode {
}
open func snapshotForReordering() -> UIView? {
return self.view.snapshotContentTree()
return self.view.snapshotContentTree(keepTransform: true)
}
}

View File

@ -131,6 +131,16 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
return false
}
private func checkInteractiveDismissWithControllers() -> Bool {
if let controller = self.container.controllers.last {
if !controller.attemptNavigation({
}) {
return false
}
}
return true
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
@ -147,7 +157,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let progress = translation / self.bounds.width
let velocity = recognizer.velocity(in: self.view).x
if velocity > 1000 || progress > 0.2 {
if (velocity > 1000 || progress > 0.2) && self.checkInteractiveDismissWithControllers() {
self.isDismissed = true
self.horizontalDismissOffset = self.bounds.width
self.dismissProgress = 1.0
@ -243,7 +253,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let duration = Double(min(0.3, velocityFactor))
let transition: ContainedViewLayoutTransition
let dismissProgress: CGFloat
if velocity.y < -0.5 || progress >= 0.5 {
if (velocity.y < -0.5 || progress >= 0.5) && self.checkInteractiveDismissWithControllers() {
dismissProgress = 1.0
targetOffset = 0.0
transition = .animated(duration: duration, curve: .easeInOut)

View File

@ -197,12 +197,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
public var willScrollToTop: (() -> Void)?
public func setReorderEntry<T: ItemListNodeEntry>(_ f: @escaping (Int, Int, [T]) -> Void) {
public func setReorderEntry<T: ItemListNodeEntry>(_ f: @escaping (Int, Int, [T]) -> Signal<Bool, NoError>) {
self.reorderEntry = { a, b, list in
f(a, b, list.map { $0 as! T })
return f(a, b, list.map { $0 as! T })
}
}
private var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Void)? {
private var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Signal<Bool, NoError>)? {
didSet {
if self.isNodeLoaded {
(self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry

View File

@ -217,7 +217,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
public var contentOffsetChanged: ((ListViewVisibleContentOffset, Bool) -> Void)?
public var contentScrollingEnded: ((ListView) -> Bool)?
public var searchActivated: ((Bool) -> Void)?
public var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Void)?
public var reorderEntry: ((Int, Int, [ItemListNodeAnyEntry]) -> Signal<Bool, NoError>)?
public var reorderCompleted: (([ItemListNodeAnyEntry]) -> Void)?
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
@ -269,7 +269,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in
if let strongSelf = self, let reorderEntry = strongSelf.reorderEntry, let mergedEntries = (opaqueTransactionState as? ItemListNodeOpaqueState)?.mergedEntries {
if fromIndex >= 0 && fromIndex < mergedEntries.count && toIndex >= 0 && toIndex < mergedEntries.count {
reorderEntry(fromIndex, toIndex, mergedEntries)
return reorderEntry(fromIndex, toIndex, mergedEntries)
}
}
return .single(false)

View File

@ -24,13 +24,15 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
let linkAction: ((ItemListTextItemLinkAction) -> Void)?
let style: ItemListStyle
public let isAlwaysPlain: Bool = true
public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks) {
public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks, tag: ItemListItemTag? = nil) {
self.presentationData = presentationData
self.text = text
self.sectionId = sectionId
self.linkAction = linkAction
self.style = style
self.tag = tag
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -70,12 +72,16 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
}
}
public class ItemListTextItemNode: ListViewItemNode {
public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode {
private let titleNode: TextNode
private let activateArea: AccessibilityAreaNode
private var item: ItemListTextItem?
public var tag: ItemListItemTag? {
return self.item?.tag
}
public init() {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
@ -111,6 +117,7 @@ public class ItemListTextItemNode: ListViewItemNode {
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseHeaderFontSize * 2.0))
let semiLargeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseHeaderFontSize * 1.2))
let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let attributedText: NSAttributedString
@ -118,7 +125,13 @@ public class ItemListTextItemNode: ListViewItemNode {
case let .plain(text):
attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor)
case let .large(text):
attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let font: UIFont
if params.width >= 330.0 {
font = largeTitleFont
} else {
font = semiLargeTitleFont
}
attributedText = NSAttributedString(string: text, font: font, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
case let .markdown(text):
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)

View File

@ -453,10 +453,10 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
dismissImpl = { [weak controller] in
controller?.dismiss()
}
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ProxySettingsControllerEntry]) -> Void in
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ProxySettingsControllerEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .server(_, _, _, fromServer, _, _, _, _) = fromEntry else {
return
return .single(false)
}
var referenceServer: ProxyServerSettings?
var beforeAll = false
@ -476,7 +476,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
afterAll = true
}
let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in
return updateProxySettingsInteractively(accountManager: accountManager, { current in
var current = current
if let index = current.servers.firstIndex(of: fromServer) {
current.servers.remove(at: index)
@ -503,7 +503,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
current.servers.append(fromServer)
}
return current
}).start()
})
})
shareProxyListImpl = { [weak controller] in

View File

@ -698,10 +698,10 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
if case .modal = mode {
controller.navigationPresentation = .modal
}
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [InstalledStickerPacksEntry]) -> Void in
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [InstalledStickerPacksEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else {
return
return .single(false)
}
var referenceId: ItemCollectionId?
var beforeAll = false
@ -731,20 +731,26 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
}
}
var previousIndex: Int?
for i in 0 ..< currentIds.count {
if currentIds[i] == fromPackInfo.id {
previousIndex = i
currentIds.remove(at: i)
break
}
}
var didReorder = false
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< currentIds.count {
if currentIds[i] == referenceId {
if fromIndex < toIndex {
didReorder = previousIndex != i + 1
currentIds.insert(fromPackInfo.id, at: i + 1)
} else {
didReorder = previousIndex != i
currentIds.insert(fromPackInfo.id, at: i)
}
inserted = true
@ -752,15 +758,20 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
}
}
if !inserted {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id)
}
} else if beforeAll {
didReorder = previousIndex != 0
currentIds.insert(fromPackInfo.id, at: 0)
} else if afterAll {
didReorder = previousIndex != currentIds.count
currentIds.append(fromPackInfo.id)
}
temporaryPackOrder.set(.single(currentIds))
return .single(didReorder)
})
controller.setReorderCompleted({ (entries: [InstalledStickerPacksEntry]) -> Void in

View File

@ -4,9 +4,9 @@ import SwiftSignalKit
import MtProtoKit
import SyncCore
public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
updateProxySettingsInteractively(transaction: transaction, f)
public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal<Bool, NoError> {
return accountManager.transaction { transaction -> Bool in
return updateProxySettingsInteractively(transaction: transaction, f)
}
}
@ -21,10 +21,13 @@ extension ProxyServerSettings {
}
}
public func updateProxySettingsInteractively(transaction: AccountManagerModifier, _ f: @escaping (ProxySettings) -> ProxySettings) {
public func updateProxySettingsInteractively(transaction: AccountManagerModifier, _ f: @escaping (ProxySettings) -> ProxySettings) -> Bool {
var hasChanges = false
transaction.updateSharedData(SharedDataKeys.proxySettings, { current in
let previous = (current as? ProxySettings) ?? ProxySettings.defaultSettings
let updated = f(previous)
hasChanges = previous != updated
return updated
})
return hasChanges
}

View File

@ -343,7 +343,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
primaryColor: UIColor(rgb: 0xffffff),
controlColor: UIColor(rgb: 0x4d4d4d)
),
mediaPlaceholderColor: UIColor(rgb: 0x1c1c1d),
mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.1),
scrollIndicatorColor: UIColor(rgb: 0xffffff, alpha: 0.3),
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
inputClearButtonColor: UIColor(rgb: 0x8b9197),
@ -388,7 +388,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
let message = PresentationThemeChatMessage(
incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x262628), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628))), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.4), mediaControlInnerBackgroundColor: UIColor(rgb: 0x262628), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff, alpha: 0.12), separator: UIColor(rgb: 0x000000), bar: UIColor(rgb: 0xffffff), barIconForeground: .white, barPositive: UIColor(rgb: 0x00A700), barNegative: UIColor(rgb: 0xFE3824)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)),
outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x313131), gradientFill: UIColor(rgb: 0x313131), highlightedFill: UIColor(rgb: 0x464646), stroke: UIColor(rgb: 0x313131)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x313131), gradientFill: UIColor(rgb: 0x313131), highlightedFill: UIColor(rgb: 0x464646), stroke: UIColor(rgb: 0x313131))), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .white, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)),
outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x313131), gradientFill: UIColor(rgb: 0x313131), highlightedFill: UIColor(rgb: 0x464646), stroke: UIColor(rgb: 0x313131)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x313131), gradientFill: UIColor(rgb: 0x313131), highlightedFill: UIColor(rgb: 0x464646), stroke: UIColor(rgb: 0x313131))), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)),
freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f))),
infoPrimaryTextColor: UIColor(rgb: 0xffffff),
infoLinkTextColor: UIColor(rgb: 0xffffff),

View File

@ -163,6 +163,7 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode {
self.animatedColor = animatedColor
updated = true
}
let wasAnimating = self.isAnimating
if isAnimating != self.isAnimating {
let previous = self.shouldBeAnimating
self.isAnimating = isAnimating
@ -171,7 +172,7 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode {
self.updateAnimating()
}
}
if isSelectable {
if isSelectable && !isAnimating {
if self.checkNode == nil {
updated = true
let checkNode = CheckNode(strokeColor: staticColor, fillColor: animatedColor, foregroundColor: foregroundColor, style: .plain)
@ -183,7 +184,13 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode {
} else if let checkNode = self.checkNode {
updated = true
self.checkNode = nil
checkNode.removeFromSupernode()
if wasAnimating != self.isAnimating {
checkNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak checkNode] _ in
checkNode?.removeFromSupernode()
})
} else {
checkNode.removeFromSupernode()
}
}
if updated {
self.setNeedsDisplay()
@ -487,6 +494,9 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
let strokeColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground
if strokeColor.alpha.isZero {
context.setBlendMode(.copy)
}
context.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(1.5)
context.setLineJoin(.round)
@ -1065,7 +1075,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
var optionNodesSizesAndApply: [(CGSize, (Bool, Bool) -> ChatMessagePollOptionNode)] = []
for finalizeLayout in pollOptionsFinalizeLayouts {
let result = finalizeLayout(boundingWidth - layoutConstants.bubble.borderInset * 2.0)
let result = finalizeLayout(boundingWidth - layoutConstants.bubble.borderInset * 2.0)
resultSize.width = max(resultSize.width, result.0.width + layoutConstants.bubble.borderInset * 2.0)
resultSize.height += result.0.height
optionNodesSizesAndApply.append(result)
@ -1076,9 +1086,18 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let votersBottomSpacing: CGFloat = 11.0
resultSize.height += optionsVotersSpacing + votersLayout.size.height + votersBottomSpacing
let buttonSubmitInactiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitInactiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitInactiveTextLayout.size)
let buttonSubmitActiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitActiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitActiveTextLayout.size)
let buttonViewResultsTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonViewResultsTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonViewResultsTextLayout.size)
var adjustedStatusFrame: CGRect?
if let statusFrame = statusFrame {
adjustedStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: resultSize.height - statusFrame.size.height - 6.0), size: statusFrame.size)
var localStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: resultSize.height - statusFrame.size.height - 6.0), size: statusFrame.size)
if localStatusFrame.minX <= buttonViewResultsTextFrame.maxX || localStatusFrame.minX <= buttonSubmitActiveTextFrame.maxX {
localStatusFrame.origin.y += 10.0
resultSize.height += 10.0
}
adjustedStatusFrame = localStatusFrame
}
return (resultSize, { [weak self] animation, synchronousLoad in
@ -1200,13 +1219,13 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = buttonSubmitInactiveTextApply()
strongSelf.buttonSubmitInactiveTextNode.frame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitInactiveTextLayout.size.width) / 2.0), y: verticalOffset + optionsButtonSpacing), size: buttonSubmitInactiveTextLayout.size)
strongSelf.buttonSubmitInactiveTextNode.frame = buttonSubmitInactiveTextFrame.offsetBy(dx: 0.0, dy: verticalOffset)
let _ = buttonSubmitActiveTextApply()
strongSelf.buttonSubmitActiveTextNode.frame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitActiveTextLayout.size.width) / 2.0), y: verticalOffset + optionsButtonSpacing), size: buttonSubmitActiveTextLayout.size)
strongSelf.buttonSubmitActiveTextNode.frame = buttonSubmitActiveTextFrame.offsetBy(dx: 0.0, dy: verticalOffset)
let _ = buttonViewResultsTextApply()
strongSelf.buttonViewResultsTextNode.frame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonViewResultsTextLayout.size.width) / 2.0), y: verticalOffset + optionsButtonSpacing), size: buttonViewResultsTextLayout.size)
strongSelf.buttonViewResultsTextNode.frame = buttonViewResultsTextFrame.offsetBy(dx: 0.0, dy: verticalOffset)
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: resultSize.width, height: 44.0))
@ -1224,10 +1243,16 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
var hasSelection = false
switch poll.kind {
case .poll(true):
hasSelection = true
default:
break
}
var hasSelectedOptions = false
for optionNode in self.optionNodes {
if let isChecked = optionNode.radioNode?.isChecked {
hasSelection = true
if isChecked {
hasSelectedOptions = true
}
@ -1241,7 +1266,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
if hasSelection && poll.pollId.namespace == Namespaces.Media.CloudPoll {
if hasSelection && !hasResults && poll.pollId.namespace == Namespaces.Media.CloudPoll {
self.votersNode.isHidden = true
self.buttonViewResultsTextNode.isHidden = true
self.buttonSubmitInactiveTextNode.isHidden = hasSelectedOptions