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

View File

@ -391,6 +391,8 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3) strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
} }
let bottomStripeWasHidden = strongSelf.bottomStripeNode.isHidden
let hasCorners = itemListHasRoundedBlockLayout(params) let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false var hasTopCorners = false
var hasBottomCorners = false var hasBottomCorners = false
@ -405,6 +407,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
switch neighbors.bottom { switch neighbors.bottom {
case .sameSection(false): case .sameSection(false):
bottomStripeInset = leftInset bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false
default: default:
bottomStripeInset = 0.0 bottomStripeInset = 0.0
hasBottomCorners = true hasBottomCorners = true
@ -418,7 +421,17 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) 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.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.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) let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit, transition)

View File

@ -435,6 +435,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
private func endReordering() { private func endReordering() {
self.itemReorderingTimer?.invalidate()
self.itemReorderingTimer = nil
self.lastReorderingOffset = nil
let f: () -> Void = { [weak self] in let f: () -> Void = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -467,11 +471,32 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
private func checkItemReordering() { private var itemReorderingTimer: SwiftSignalKit.Timer?
if let reorderNode = self.reorderNode, let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self { private var lastReorderingOffset: CGFloat?
guard let verticalTopOffset = reorderNode.currentOffset() else {
return 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 let verticalOffset = verticalTopOffset
var closestIndex: (Int, CGFloat)? var closestIndex: (Int, CGFloat)?
for i in 0 ..< self.itemNodes.count { for i in 0 ..< self.itemNodes.count {

View File

@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode {
} }
open func snapshotForReordering() -> UIView? { 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 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) { @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .began: case .began:
@ -147,7 +157,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let progress = translation / self.bounds.width let progress = translation / self.bounds.width
let velocity = recognizer.velocity(in: self.view).x 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.isDismissed = true
self.horizontalDismissOffset = self.bounds.width self.horizontalDismissOffset = self.bounds.width
self.dismissProgress = 1.0 self.dismissProgress = 1.0
@ -243,7 +253,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
let duration = Double(min(0.3, velocityFactor)) let duration = Double(min(0.3, velocityFactor))
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
let dismissProgress: CGFloat let dismissProgress: CGFloat
if velocity.y < -0.5 || progress >= 0.5 { if (velocity.y < -0.5 || progress >= 0.5) && self.checkInteractiveDismissWithControllers() {
dismissProgress = 1.0 dismissProgress = 1.0
targetOffset = 0.0 targetOffset = 0.0
transition = .animated(duration: duration, curve: .easeInOut) transition = .animated(duration: duration, curve: .easeInOut)

View File

@ -197,12 +197,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
public var willScrollToTop: (() -> Void)? 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 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 { didSet {
if self.isNodeLoaded { if self.isNodeLoaded {
(self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry (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 contentOffsetChanged: ((ListViewVisibleContentOffset, Bool) -> Void)?
public var contentScrollingEnded: ((ListView) -> Bool)? public var contentScrollingEnded: ((ListView) -> Bool)?
public var searchActivated: ((Bool) -> Void)? 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 reorderCompleted: (([ItemListNodeAnyEntry]) -> Void)?
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)? public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
@ -269,7 +269,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in self.listNode.reorderItem = { [weak self] fromIndex, toIndex, opaqueTransactionState in
if let strongSelf = self, let reorderEntry = strongSelf.reorderEntry, let mergedEntries = (opaqueTransactionState as? ItemListNodeOpaqueState)?.mergedEntries { 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 { if fromIndex >= 0 && fromIndex < mergedEntries.count && toIndex >= 0 && toIndex < mergedEntries.count {
reorderEntry(fromIndex, toIndex, mergedEntries) return reorderEntry(fromIndex, toIndex, mergedEntries)
} }
} }
return .single(false) return .single(false)

View File

@ -24,13 +24,15 @@ public class ItemListTextItem: ListViewItem, ItemListItem {
let linkAction: ((ItemListTextItemLinkAction) -> Void)? let linkAction: ((ItemListTextItemLinkAction) -> Void)?
let style: ItemListStyle let style: ItemListStyle
public let isAlwaysPlain: Bool = true 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.presentationData = presentationData
self.text = text self.text = text
self.sectionId = sectionId self.sectionId = sectionId
self.linkAction = linkAction self.linkAction = linkAction
self.style = style 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) { 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 titleNode: TextNode
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
private var item: ItemListTextItem? private var item: ItemListTextItem?
public var tag: ItemListItemTag? {
return self.item?.tag
}
public init() { public init() {
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
@ -111,6 +117,7 @@ public class ItemListTextItemNode: ListViewItemNode {
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseHeaderFontSize * 2.0)) 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 titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let attributedText: NSAttributedString let attributedText: NSAttributedString
@ -118,7 +125,13 @@ public class ItemListTextItemNode: ListViewItemNode {
case let .plain(text): case let .plain(text):
attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor) attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor)
case let .large(text): 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): 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 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) return (TelegramTextAttributes.URL, contents)

View File

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

View File

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

View File

@ -4,9 +4,9 @@ import SwiftSignalKit
import MtProtoKit import MtProtoKit
import SyncCore import SyncCore
public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal<Void, NoError> { public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal<Bool, NoError> {
return accountManager.transaction { transaction -> Void in return accountManager.transaction { transaction -> Bool in
updateProxySettingsInteractively(transaction: transaction, f) 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 transaction.updateSharedData(SharedDataKeys.proxySettings, { current in
let previous = (current as? ProxySettings) ?? ProxySettings.defaultSettings let previous = (current as? ProxySettings) ?? ProxySettings.defaultSettings
let updated = f(previous) let updated = f(previous)
hasChanges = previous != updated
return updated return updated
}) })
return hasChanges
} }

View File

@ -343,7 +343,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
primaryColor: UIColor(rgb: 0xffffff), primaryColor: UIColor(rgb: 0xffffff),
controlColor: UIColor(rgb: 0x4d4d4d) controlColor: UIColor(rgb: 0x4d4d4d)
), ),
mediaPlaceholderColor: UIColor(rgb: 0x1c1c1d), mediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.1),
scrollIndicatorColor: UIColor(rgb: 0xffffff, alpha: 0.3), scrollIndicatorColor: UIColor(rgb: 0xffffff, alpha: 0.3),
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3), pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
inputClearButtonColor: UIColor(rgb: 0x8b9197), inputClearButtonColor: UIColor(rgb: 0x8b9197),
@ -388,7 +388,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
let message = PresentationThemeChatMessage( 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)), 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))), 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), infoPrimaryTextColor: UIColor(rgb: 0xffffff),
infoLinkTextColor: UIColor(rgb: 0xffffff), infoLinkTextColor: UIColor(rgb: 0xffffff),

View File

@ -163,6 +163,7 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode {
self.animatedColor = animatedColor self.animatedColor = animatedColor
updated = true updated = true
} }
let wasAnimating = self.isAnimating
if isAnimating != self.isAnimating { if isAnimating != self.isAnimating {
let previous = self.shouldBeAnimating let previous = self.shouldBeAnimating
self.isAnimating = isAnimating self.isAnimating = isAnimating
@ -171,7 +172,7 @@ private final class ChatMessagePollOptionRadioNode: ASDisplayNode {
self.updateAnimating() self.updateAnimating()
} }
} }
if isSelectable { if isSelectable && !isAnimating {
if self.checkNode == nil { if self.checkNode == nil {
updated = true updated = true
let checkNode = CheckNode(strokeColor: staticColor, fillColor: animatedColor, foregroundColor: foregroundColor, style: .plain) 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 { } else if let checkNode = self.checkNode {
updated = true updated = true
self.checkNode = nil 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 { if updated {
self.setNeedsDisplay() self.setNeedsDisplay()
@ -487,6 +494,9 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) 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 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.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(1.5) context.setLineWidth(1.5)
context.setLineJoin(.round) context.setLineJoin(.round)
@ -1065,7 +1075,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
var optionNodesSizesAndApply: [(CGSize, (Bool, Bool) -> ChatMessagePollOptionNode)] = [] var optionNodesSizesAndApply: [(CGSize, (Bool, Bool) -> ChatMessagePollOptionNode)] = []
for finalizeLayout in pollOptionsFinalizeLayouts { 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.width = max(resultSize.width, result.0.width + layoutConstants.bubble.borderInset * 2.0)
resultSize.height += result.0.height resultSize.height += result.0.height
optionNodesSizesAndApply.append(result) optionNodesSizesAndApply.append(result)
@ -1076,9 +1086,18 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let votersBottomSpacing: CGFloat = 11.0 let votersBottomSpacing: CGFloat = 11.0
resultSize.height += optionsVotersSpacing + votersLayout.size.height + votersBottomSpacing 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? var adjustedStatusFrame: CGRect?
if let statusFrame = statusFrame { 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 return (resultSize, { [weak self] animation, synchronousLoad in
@ -1200,13 +1219,13 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
} }
let _ = buttonSubmitInactiveTextApply() 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() 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() 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)) 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 var hasSelection = false
switch poll.kind {
case .poll(true):
hasSelection = true
default:
break
}
var hasSelectedOptions = false var hasSelectedOptions = false
for optionNode in self.optionNodes { for optionNode in self.optionNodes {
if let isChecked = optionNode.radioNode?.isChecked { if let isChecked = optionNode.radioNode?.isChecked {
hasSelection = true
if isChecked { if isChecked {
hasSelectedOptions = true 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.votersNode.isHidden = true
self.buttonViewResultsTextNode.isHidden = true self.buttonViewResultsTextNode.isHidden = true
self.buttonSubmitInactiveTextNode.isHidden = hasSelectedOptions self.buttonSubmitInactiveTextNode.isHidden = hasSelectedOptions