diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index f66429cf3a..de43ccc426 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -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 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? + 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 diff --git a/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift b/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift index 45adac7e97..8661a2da7d 100644 --- a/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift +++ b/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift @@ -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) diff --git a/submodules/Display/Display/ListView.swift b/submodules/Display/Display/ListView.swift index ea18fb4f9a..fd2b2ded3a 100644 --- a/submodules/Display/Display/ListView.swift +++ b/submodules/Display/Display/ListView.swift @@ -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 { diff --git a/submodules/Display/Display/ListViewItemNode.swift b/submodules/Display/Display/ListViewItemNode.swift index a2a920f641..608580a667 100644 --- a/submodules/Display/Display/ListViewItemNode.swift +++ b/submodules/Display/Display/ListViewItemNode.swift @@ -564,6 +564,6 @@ open class ListViewItemNode: ASDisplayNode { } open func snapshotForReordering() -> UIView? { - return self.view.snapshotContentTree() + return self.view.snapshotContentTree(keepTransform: true) } } diff --git a/submodules/Display/Display/Navigation/NavigationModalContainer.swift b/submodules/Display/Display/Navigation/NavigationModalContainer.swift index 65c7d2233c..0f3f0dee97 100644 --- a/submodules/Display/Display/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Display/Navigation/NavigationModalContainer.swift @@ -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) diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 4d64b03c76..893e09c32a 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -197,12 +197,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable public var willScrollToTop: (() -> Void)? - public func setReorderEntry(_ f: @escaping (Int, Int, [T]) -> Void) { + public func setReorderEntry(_ f: @escaping (Int, Int, [T]) -> Signal) { 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)? { didSet { if self.isNodeLoaded { (self.displayNode as! ItemListControllerNode).reorderEntry = self.reorderEntry diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index a56087ca9e..7f99f946e5 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -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)? 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) diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift index 32307fdf36..6344e1bdca 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift @@ -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?, (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) diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 317eedd81f..c0424de212 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -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 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 diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index a3ee302d57..84a7c355c4 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -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 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 diff --git a/submodules/TelegramCore/Sources/ProxySettings.swift b/submodules/TelegramCore/Sources/ProxySettings.swift index 82253e7fb4..933f79dc14 100644 --- a/submodules/TelegramCore/Sources/ProxySettings.swift +++ b/submodules/TelegramCore/Sources/ProxySettings.swift @@ -4,9 +4,9 @@ import SwiftSignalKit import MtProtoKit import SyncCore -public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal { - return accountManager.transaction { transaction -> Void in - updateProxySettingsInteractively(transaction: transaction, f) +public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal { + 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 } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index ae70b59e7b..ff9250a2c3 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -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), diff --git a/submodules/TelegramUI/TelegramUI/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessagePollBubbleContentNode.swift index bd23c5b8c1..be7e8bfb4a 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessagePollBubbleContentNode.swift @@ -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