mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Poll UI improvements
This commit is contained in:
parent
0e792f0502
commit
2a6f1f811b
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
open func snapshotForReordering() -> UIView? {
|
||||
return self.view.snapshotContentTree()
|
||||
return self.view.snapshotContentTree(keepTransform: true)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user