UI improvements

This commit is contained in:
Ilya Laktyushin 2023-03-20 15:24:40 +04:00
parent 7269255d00
commit c02f796791
12 changed files with 163 additions and 73 deletions

View file

@ -9081,3 +9081,9 @@ Sorry for the inconvenience.";
"CreateGroup.PeersTitleDelimeter" = ", ";
"CreateGroup.PeersTitleLastDelimeter" = " and ";
"PeerInfo.CancelSelectionAlertText" = "Cancel selection?";
"PeerInfo.CancelSelectionAlertYes" = "Yes";
"PeerInfo.CancelSelectionAlertNo" = "No";
"StickerPacksSettings.SuggestAnimatedEmojiInfo" = "Each time you enter an emoji you can replace it with an animated emoji.";

View file

@ -2561,10 +2561,6 @@ public final class ChatListNode: ListView {
return strongSelf.isSelectionGestureEnabled
}
self.view.addGestureRecognizer(selectionRecognizer)
// if case .forum = location {
// self.isSelectionGestureEnabled = false
// }
}
deinit {

View file

@ -139,6 +139,11 @@ open class ViewControllerComponentContainer: ViewController {
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) {
self.currentLayout = (layout, navigationHeight)
var theme = self.theme ?? self.presentationData.theme
if theme.list.blocksBackgroundColor.rgb == theme.list.plainBackgroundColor.rgb {
theme = theme.withModalBlocksBackground()
}
let environment = ViewControllerComponentContainer.Environment(
statusBarHeight: layout.statusBarHeight ?? 0.0,
navigationHeight: navigationHeight,
@ -147,7 +152,7 @@ open class ViewControllerComponentContainer: ViewController {
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,
isVisible: self.currentIsVisible,
theme: self.theme ?? self.presentationData.theme,
theme: theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
controller: { [weak self] in

View file

@ -248,7 +248,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.gridNode)
// self.containerNode.addSubnode(self.scrollingArea)
//self.containerNode.addSubnode(self.scrollingArea)
let preloadPromise = self.preloadPromise
let updatedState: Signal<State, NoError>
@ -359,30 +359,36 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.itemsDimensionsUpdatedDisposable?.dispose()
}
private var selectionGesture: MediaPickerGridSelectionGesture?
private var selectionGesture: MediaPickerGridSelectionGesture<TGMediaSelectableItem>?
override func didLoad() {
super.didLoad()
self.gridNode.scrollView.alwaysBounceVertical = true
self.gridNode.scrollView.showsVerticalScrollIndicator = false
self.selectionGesture = MediaPickerGridSelectionGesture(target: nil, action: nil, gridNode: self.gridNode)
self.selectionGesture?.delegate = self
self.selectionGesture?.began = { [weak self] in
let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>()
selectionGesture.delegate = self
selectionGesture.began = { [weak self] in
self?.controller?.cancelPanGesture()
}
self.selectionGesture?.itemAt = { [weak self] point in
selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in
self?.gridNode.scrollView.isScrollEnabled = isEnabled
}
selectionGesture.itemAt = { [weak self] point in
if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem {
return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false)
} else {
return nil
}
}
self.selectionGesture?.updateSelection = { [weak self] asset, selected in
selectionGesture.updateSelection = { [weak self] asset, selected in
if let strongSelf = self {
strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil)
}
}
selectionGesture.sideInset = 44.0
self.gridNode.view.addGestureRecognizer(selectionGesture)
self.selectionGesture = selectionGesture
if let controller = self.controller, case let .assets(collection) = controller.subject, collection != nil {
self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in
@ -1546,7 +1552,6 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
let controller = textAlertController(context: self.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertNo, action: {
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, action: { [weak self] in
self?.dismissAllTooltips()
completion()
@ -1794,37 +1799,31 @@ private final class MediaPickerContextReferenceContentSource: ContextReferenceCo
}
}
private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer {
var itemAt: (CGPoint) -> (TGMediaSelectableItem, Bool)? = { _ in return nil }
var updateSelection: (TGMediaSelectableItem, Bool) -> Void = { _, _ in}
var began: () -> Void = {}
private weak var gridNode: GridNode?
public class MediaPickerGridSelectionGesture<T> : UIPanGestureRecognizer {
public var itemAt: (CGPoint) -> (T, Bool)? = { _ in return nil }
public var updateSelection: (T, Bool) -> Void = { _, _ in}
public var updateIsScrollEnabled: (Bool) -> Void = { _ in}
public var began: () -> Void = {}
private var processing = false
private var selecting = false
private var initialLocation: CGPoint?
var sideInset: CGFloat = 0.0
public var sideInset: CGFloat = 0.0
init(target: Any?, action: Selector?, gridNode: GridNode) {
self.gridNode = gridNode
super.init(target: target, action: action)
gridNode.view.addGestureRecognizer(self)
public init() {
super.init(target: nil, action: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first, self.numberOfTouches == 1, let gridNode = self.gridNode else {
guard let touch = touches.first, self.numberOfTouches == 1 else {
return
}
let location = touch.location(in: gridNode.view)
let location = touch.location(in: self.view)
if location.x > self.sideInset {
self.initialLocation = location
} else {
@ -1832,15 +1831,15 @@ private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer {
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
guard let touch = touches.first, let gridNode = self.gridNode, let initialLocation = self.initialLocation else {
guard let touch = touches.first, let initialLocation = self.initialLocation else {
self.state = .failed
return
}
let location = touch.location(in: gridNode.view)
let location = touch.location(in: self.view)
let translation = CGPoint(x: location.x - initialLocation.x, y: location.y - initialLocation.y)
var additionalLocation: CGPoint?
@ -1849,7 +1848,7 @@ private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer {
self.state = .failed
} else if abs(translation.x) > 8.0 {
self.processing = true
self.gridNode?.scrollView.isScrollEnabled = false
self.updateIsScrollEnabled(false)
self.began()
if let (_, selected) = self.itemAt(location) {
@ -1873,25 +1872,25 @@ private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer {
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
self.state = .failed
self.reset()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
self.state = .failed
self.reset()
}
override func reset() {
public override func reset() {
super.reset()
self.processing = false
self.initialLocation = nil
self.gridNode?.scrollView.isScrollEnabled = true
self.updateIsScrollEnabled(true)
}
}

View file

@ -593,7 +593,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
strongSelf.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
completion()
})
strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
for (_, backgroundNode) in strongSelf.backgroundNodes {
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
@ -641,7 +641,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
}
})
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
for (_, backgroundNode) in self.backgroundNodes {
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)

View file

@ -167,7 +167,6 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
static var body: Body {
let overscroll = Child(Rectangle.self)
let fade = Child(RoundedRectangle.self)
let text = Child(MultilineTextComponent.self)
let optionsSection = Child(SectionGroupComponent.self)
let perksSection = Child(SectionGroupComponent.self)
@ -195,23 +194,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
context.add(overscroll
.position(CGPoint(x: overscroll.size.width / 2.0, y: -overscroll.size.height / 2.0))
)
let fade = fade.update(
component: RoundedRectangle(
colors: [
theme.list.plainBackgroundColor,
theme.list.blocksBackgroundColor
],
cornerRadius: 0.0,
gradientDirection: .vertical
),
availableSize: CGSize(width: availableWidth, height: 300),
transition: context.transition
)
context.add(fade
.position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0))
)
size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0
let textColor = theme.list.itemPrimaryTextColor

View file

@ -45,13 +45,14 @@ public class ItemListReactionItem: ListViewItem, ItemListItem {
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) {
async {
let node = ItemListReactionItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
let node = ItemListReactionItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
completion(node, {
return (nil, { _ in apply() })
})

View file

@ -92,6 +92,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case packOrder(PresentationTheme, String, Bool)
case packOrderInfo(PresentationTheme, String)
case suggestAnimatedEmoji(String, Bool)
case suggestAnimatedEmojiInfo(PresentationTheme, String)
case packsTitle(PresentationTheme, String)
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, Bool, ItemListStickerPackItemEditing, Bool?)
case packsInfo(PresentationTheme, String)
@ -100,7 +101,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
switch self {
case .trending, .masks, .emoji, .quickReaction, .archived:
return InstalledStickerPacksSection.categories.rawValue
case .suggestOptions, .largeEmoji, .suggestAnimatedEmoji, .packOrder, .packOrderInfo:
case .suggestOptions, .largeEmoji, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo, .packOrder, .packOrderInfo:
return InstalledStickerPacksSection.settings.rawValue
case .packsTitle, .pack, .packsInfo:
return InstalledStickerPacksSection.stickers.rawValue
@ -125,16 +126,18 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
return .index(6)
case .suggestAnimatedEmoji:
return .index(7)
case .packOrder:
case .suggestAnimatedEmojiInfo:
return .index(8)
case .packOrderInfo:
case .packOrder:
return .index(9)
case .packsTitle:
case .packOrderInfo:
return .index(10)
case .packsTitle:
return .index(11)
case let .pack(_, _, _, info, _, _, _, _, _, _):
return .pack(info.id)
case .packsInfo:
return .index(11)
return .index(12)
}
}
@ -200,6 +203,12 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
} else {
return false
}
case let .suggestAnimatedEmojiInfo(lhsTheme, lhsText):
if case let .suggestAnimatedEmojiInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .packsTitle(lhsTheme, lhsText):
if case let .packsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -323,9 +332,16 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
default:
return true
}
case .suggestAnimatedEmojiInfo:
switch rhs {
case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo:
return false
default:
return true
}
case .packsTitle:
switch rhs {
case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .packsTitle:
case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo, .packsTitle:
return false
default:
return true
@ -390,6 +406,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleSuggestAnimatedEmoji(value)
})
case let .suggestAnimatedEmojiInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .packsTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected):
@ -531,6 +549,7 @@ private func installedStickerPacksControllerEntries(context: AccountContext, pre
}
entries.append(.suggestAnimatedEmoji(presentationData.strings.StickerPacksSettings_SuggestAnimatedEmoji, stickerSettings.suggestAnimatedEmoji))
entries.append(.suggestAnimatedEmojiInfo(presentationData.theme, presentationData.strings.StickerPacksSettings_SuggestAnimatedEmojiInfo))
}
if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespaceForMode(mode)])] as? ItemCollectionInfosView {

View file

@ -1344,6 +1344,12 @@ public final class SparseItemGrid: ASDisplayNode {
self.pinchRecognizer?.isEnabled = self.pinchEnabled
}
}
public var isScrollEnabled: Bool = true {
didSet {
self.currentViewport?.scrollView.isScrollEnabled = self.isScrollEnabled
}
}
public init(theme: PresentationTheme, initialZoomLevel: ZoomLevel? = nil) {
self.theme = theme

View file

@ -25,6 +25,7 @@ import CheckNode
import AppBundle
import ChatControllerInteraction
import InvisibleInkDustNode
import MediaPickerUI
private final class FrameSequenceThumbnailNode: ASDisplayNode {
private let context: AccountContext
@ -1631,7 +1632,7 @@ private func tagMaskForType(_ type: PeerInfoVisualMediaPaneNode.ContentType) ->
}
}
final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
enum ContentType {
case photoOrVideo
case photo
@ -2445,6 +2446,27 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
self.itemGrid.addToTransitionSurface(view: view)
}
private var gridSelectionGesture: MediaPickerGridSelectionGesture<EngineMessage.Id>?
private var listSelectionGesture: MediaPickerGridSelectionGesture<EngineMessage.Id>?
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let location = gestureRecognizer.location(in: gestureRecognizer.view)
if location.x < 44.0 {
return false
}
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.state != .failed, let otherGestureRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer {
otherGestureRecognizer.isEnabled = false
otherGestureRecognizer.isEnabled = true
return true
} else {
return false
}
}
func updateSelectedMessages(animated: Bool) {
switch self.contentType {
case .files, .music, .voiceAndVideoMessages:
@ -2473,7 +2495,36 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
itemLayer.updateSelection(theme: self.itemGridBinding.checkNodeTheme, isSelected: self.chatControllerInteraction.selectionState?.selectedIds.contains(item.message.id), animated: animated)
}
self.itemGrid.pinchEnabled = self.chatControllerInteraction.selectionState == nil
let isSelecting = self.chatControllerInteraction.selectionState != nil
self.itemGrid.pinchEnabled = !isSelecting
if isSelecting {
if self.gridSelectionGesture == nil {
let selectionGesture = MediaPickerGridSelectionGesture<EngineMessage.Id>()
selectionGesture.delegate = self
selectionGesture.sideInset = 44.0
selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in
self?.itemGrid.isScrollEnabled = isEnabled
}
selectionGesture.itemAt = { [weak self] point in
if let strongSelf = self, let itemLayer = strongSelf.itemGrid.item(at: point)?.layer as? ItemLayer, let messageId = itemLayer.item?.message.id {
return (messageId, strongSelf.chatControllerInteraction.selectionState?.selectedIds.contains(messageId) ?? false)
} else {
return nil
}
}
selectionGesture.updateSelection = { [weak self] messageId, selected in
if let strongSelf = self {
strongSelf.chatControllerInteraction.toggleMessagesSelection([messageId], selected)
}
}
self.itemGrid.view.addGestureRecognizer(selectionGesture)
self.gridSelectionGesture = selectionGesture
}
} else if let gridSelectionGesture = self.gridSelectionGesture {
self.itemGrid.view.removeGestureRecognizer(gridSelectionGesture)
self.gridSelectionGesture = nil
}
}
}

View file

@ -2101,7 +2101,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, peer: Peer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, cachedData: CachedPeerData?, isContact: Bool, isSettings: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat {
let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 13.0), size: CGSize(width: avatarSize, height: avatarSize))
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 22.0), size: CGSize(width: avatarSize, height: avatarSize))
transition.updateFrameAdditiveToCenter(node: self.avatarNode, frame: CGRect(origin: avatarFrame.center, size: CGSize()))
var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0

View file

@ -9313,6 +9313,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
return result
}
fileprivate func presentSelectionDiscardAlert(action: @escaping () -> Void = {}) -> Bool {
if let selectedIds = self.chatInterfaceInteraction.selectionState?.selectedIds, !selectedIds.isEmpty {
self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.PeerInfo_CancelSelectionAlertText, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.PeerInfo_CancelSelectionAlertNo, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.PeerInfo_CancelSelectionAlertYes, action: {
action()
})]), in: .window(.root))
return true
}
return false
}
}
public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortcutResponder {
@ -9666,6 +9676,20 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
}
}
})
if !isSettings {
self.attemptNavigation = { [weak self] action in
guard let strongSelf = self else {
return true
}
if strongSelf.controllerNode.presentSelectionDiscardAlert(action: action) {
return false
}
return true
}
}
}
required init(coder aDecoder: NSCoder) {