diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a13b57cb77..8134bd8295 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7307,25 +7307,13 @@ Sorry for the inconvenience."; "Attachment.OpenCamera" = "Open Camera"; "Attachment.DeselectedPhotos_1" = "%@ photo deselected"; -"Attachment.DeselectedPhotos_2" = "%@ photos deselected"; -"Attachment.DeselectedPhotos_3_10" = "%@ photos deselected"; "Attachment.DeselectedPhotos_any" = "%@ photos deselected"; -"Attachment.DeselectedPhotos_many" = "%@ photos deselected"; -"Attachment.DeselectedPhotos_0" = "%@ photos deselected"; "Attachment.DeselectedVideos_1" = "%@ video deselected"; -"Attachment.DeselectedVideos_2" = "%@ videos deselected"; -"Attachment.DeselectedVideos_3_10" = "%@ videos deselected"; "Attachment.DeselectedVideos_any" = "%@ videos deselected"; -"Attachment.DeselectedVideos_many" = "%@ videos deselected"; -"Attachment.DeselectedVideos_0" = "%@ videos deselected"; "Attachment.DeselectedItems_1" = "%@ item deselected"; -"Attachment.DeselectedItems_2" = "%@ items deselected"; -"Attachment.DeselectedItems_3_10" = "%@ items deselected"; "Attachment.DeselectedItems_any" = "%@ items deselected"; -"Attachment.DeselectedItems_many" = "%@ items deselected"; -"Attachment.DeselectedItems_0" = "%@ items deselected"; "PrivacyPhoneNumberSettings.CustomPublicLink" = "Users who have your number saved in their contacts will also see it on Telegram.\n\nThis public link opens a chat with you:\n[https://t.me/%@]()"; @@ -7357,3 +7345,6 @@ Sorry for the inconvenience."; "Attachment.MyAlbums" = "My Albums"; "Attachment.MediaTypes" = "Media Types"; + +"Attachment.LocationAccessTitle" = "Access Your Location"; +"Attachment.LocationAccessText" = "Share places or your live location."; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 001f493fa0..522ee00d63 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -289,7 +289,6 @@ public class AttachmentController: ViewController { self.currentType = type self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in if let strongSelf = self { - strongSelf.mediaPickerContext = mediaPickerContext if let controller = controller { strongSelf.controller?._ready.set(controller.ready.get()) controller._presentedInModal = true @@ -346,6 +345,7 @@ public class AttachmentController: ViewController { strongSelf.switchingController = false } } + strongSelf.mediaPickerContext = mediaPickerContext } }) } @@ -419,7 +419,6 @@ public class AttachmentController: ViewController { } } - private var isCollapsed: Bool = false private var isUpdatingContainer = false private var switchingController = false func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -427,22 +426,15 @@ public class AttachmentController: ViewController { transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size)) - if self.modalProgress < 0.5 { - self.isCollapsed = false - } else if self.modalProgress == 1.0 { - self.isCollapsed = true - } - var containerLayout = layout let containerRect: CGRect if case .regular = layout.metrics.widthClass { let size = CGSize(width: 390.0, height: 620.0) + let insets = layout.insets(options: [.input]) let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) - var position: CGPoint = CGPoint(x: masterWidth - 174.0, y: layout.size.height - size.height - layout.intrinsicInsets.bottom - 40.0) - if let inputHeight = layout.inputHeight { - position.y -= inputHeight - } + let position: CGPoint = CGPoint(x: masterWidth - 174.0, y: layout.size.height - size.height - insets.bottom - 40.0) + containerRect = CGRect(origin: position, size: size) containerLayout.size = containerRect.size containerLayout.intrinsicInsets.bottom = 12.0 @@ -469,8 +461,8 @@ public class AttachmentController: ViewController { } - let isEffecitvelyCollapsedUpdated = (self.isCollapsed || self.selectionCount > 0) != (self.panel.isCollapsed || self.panel.isSelecting) - let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isCollapsed: self.isCollapsed, isSelecting: self.selectionCount > 0, transition: transition) + let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) + let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, transition: transition) var panelTransition = transition if isEffecitvelyCollapsedUpdated { panelTransition = .animated(duration: 0.25, curve: .easeInOut) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 5a9f15621e..aa587350eb 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -169,7 +169,6 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private var buttons: [AttachmentButtonType] = [] private var selectedIndex: Int = 0 - private(set) var isCollapsed: Bool = false private(set) var isSelecting: Bool = false private var validLayout: ContainerViewLayout? @@ -383,7 +382,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) }) if let layout = strongSelf.validLayout { - let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isCollapsed: strongSelf.isCollapsed, isSelecting: strongSelf.isSelecting, transition: .immediate) + let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isSelecting: strongSelf.isSelecting, transition: .immediate) } } }) @@ -551,12 +550,9 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } } - func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isCollapsed: Bool, isSelecting: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { + func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = layout self.buttons = buttons - - let isCollapsedUpdated = self.isCollapsed != isCollapsed - self.isCollapsed = isCollapsed let isSelectingUpdated = self.isSelecting != isSelecting self.isSelecting = isSelecting @@ -605,7 +601,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { containerFrame = bounds } let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size) - if isCollapsedUpdated || isSelectingUpdated { + if isSelectingUpdated { containerTransition = .animated(duration: 0.25, curve: .easeInOut) } else { containerTransition = transition @@ -635,13 +631,9 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.backgroundNode.update(size: containerBounds.size, transition: transition) containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) - let _ = self.updateScrollLayoutIfNeeded(force: isCollapsedUpdated || isSelectingUpdated, transition: containerTransition) + let _ = self.updateScrollLayoutIfNeeded(force: isSelectingUpdated, transition: containerTransition) - var buttonTransition: Transition = .immediate - if isCollapsedUpdated { - buttonTransition = .easeInOut(duration: 0.25) - } - self.updateViews(transition: buttonTransition) + self.updateViews(transition: .immediate) return containerFrame.height } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 79e57cac6b..9ed4b3282a 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -1499,7 +1499,10 @@ public final class ContactListNode: ASDisplayNode { self.indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition) } - self.authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition) + let permissionSize = CGSize(width: layout.size.width, height: layout.size.height - 160.0) + var permissionInsets = insets + permissionInsets.bottom += 100.0 + self.authorizationNode.updateLayout(size: permissionSize, insets: permissionInsets, transition: transition) transition.updateFrame(node: self.authorizationNode, frame: self.bounds) if !hadValidLayout { diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.swift b/submodules/DeviceAccess/Sources/DeviceAccess.swift index 6f5642e2da..dd0382288d 100644 --- a/submodules/DeviceAccess/Sources/DeviceAccess.swift +++ b/submodules/DeviceAccess/Sources/DeviceAccess.swift @@ -20,7 +20,6 @@ public enum DeviceAccessCameraSubject { case qrCode } - public enum DeviceAccessMicrophoneSubject { case audio case video diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 9a83f867d7..940d5b0f62 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -1056,7 +1056,7 @@ open class NavigationBar: ASDisplayNode { var rightTitleInset: CGFloat = rightInset + 1.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) - leftTitleInset += backButtonSize.width + backButtonInset + 1.0 + leftTitleInset = backButtonSize.width + backButtonInset + 1.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 self.backButtonNode.hitTestSlop = UIEdgeInsets(top: -topHitTestSlop, left: -27.0, bottom: -topHitTestSlop, right: -8.0) diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 9cfb50e01e..86caa402ea 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -900,7 +900,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel))) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel - nodeLayout.insets.bottom)) if let backgroundNode = strongSelf.backgroundNode { backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.size.height - nodeLayout.insets.bottom)) diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD index eb788ecccd..4d8245d7ad 100644 --- a/submodules/LocationUI/BUILD +++ b/submodules/LocationUI/BUILD @@ -43,6 +43,8 @@ swift_library( "//submodules/TooltipUI:TooltipUI", "//submodules/UndoUI:UndoUI", "//submodules/AttachmentUI:AttachmentUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", ], visibility = [ "//visibility:public", diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index ce9f7f7bb5..8c3a441075 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -294,7 +294,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab return } - self.displayNode = LocationPickerControllerNode(context: self.context, presentationData: self.presentationData, mode: self.mode, interaction: interaction, locationManager: self.locationManager) + self.displayNode = LocationPickerControllerNode(controller: self, context: self.context, presentationData: self.presentationData, mode: self.mode, interaction: interaction, locationManager: self.locationManager) self.displayNodeDidLoad() self.controllerNode.beganInteractiveDragging = { [weak self] in self?.requestAttachmentMenuExpansion() @@ -324,6 +324,8 @@ public final class LocationPickerController: ViewController, AttachmentContainab }) self.navigationBar?.passthroughTouches = false + + self.updateTabBarAlpha(1.0, .immediate) } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -349,6 +351,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab } public func prepareForReuse() { - self.updateTabBarAlpha(1.0, .animated(duration: 0.25, curve: .easeInOut)) + self.updateTabBarAlpha(1.0, .immediate) } } diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 3e22b6a739..72508b23da 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -240,7 +240,44 @@ struct LocationPickerState { } } +private class LocationContext: NSObject, CLLocationManagerDelegate { + private let locationManager: CLLocationManager + + private let accessSink = ValuePipe() + + override init() { + self.locationManager = CLLocationManager() + + super.init() + + self.locationManager.delegate = self + } + + func locationAccess() -> Signal { + let initialStatus: CLAuthorizationStatus + if #available(iOS 14.0, *) { + initialStatus = self.locationManager.authorizationStatus + } else { + initialStatus = CLLocationManager.authorizationStatus() + } + return .single(initialStatus) + |> then( + self.accessSink.signal() + ) + } + + @available(iOS 14.0, *) + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + self.accessSink.putNext(manager.authorizationStatus) + } + + func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + self.accessSink.putNext(status) + } +} + final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationManagerDelegate { + private weak var controller: LocationPickerController? private let context: AccountContext private var presentationData: PresentationData private let presentationDataPromise: Promise @@ -248,6 +285,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private let interaction: LocationPickerInteraction private let locationManager: LocationManager + private let locationContext: LocationContext + private let listNode: ListView private let emptyResultsTextNode: ImmediateTextNode private let headerNode: LocationMapHeaderNode @@ -257,6 +296,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private let optionsNode: LocationOptionsNode private(set) var searchContainerNode: LocationSearchContainerNode? + private var placeholderBackgroundNode: NavigationBackgroundNode? + private var placeholderNode: LocationPlaceholderNode? + private var locationAccessDenied = true + private var enqueuedTransitions: [LocationPickerTransaction] = [] private var disposable: Disposable? @@ -271,7 +314,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM var beganInteractiveDragging: () -> Void = {} - init(context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction, locationManager: LocationManager) { + init(controller: LocationPickerController, context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction, locationManager: LocationManager) { + self.controller = controller self.context = context self.presentationData = presentationData self.presentationDataPromise = Promise(presentationData) @@ -279,6 +323,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.interaction = interaction self.locationManager = locationManager + self.locationContext = LocationContext() + self.state = LocationPickerState() self.statePromise = Promise(self.state) @@ -448,8 +494,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: []) let previousEntries = Atomic<[LocationPickerEntry]?>(value: nil) - self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), userLocation, venues, foundVenues) - |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, userLocation, venues, foundVenuesAndLocation in + self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), userLocation, venues, foundVenues, self.locationContext.locationAccess()) + |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, userLocation, venues, foundVenuesAndLocation, access in if let strongSelf = self { let (foundVenues, foundVenuesLocation) = foundVenuesAndLocation ?? (nil, nil) @@ -589,6 +635,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM if let (layout, navigationBarHeight) = strongSelf.validLayout { var updateLayout = false let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) + + if [.denied, .restricted].contains(access) { + if !strongSelf.locationAccessDenied { + strongSelf.locationAccessDenied = true + updateLayout = true + } + } else { + if strongSelf.locationAccessDenied { + strongSelf.locationAccessDenied = false + updateLayout = true + } + } if previousState.displayingMapModeOptions != state.displayingMapModeOptions { updateLayout = true @@ -892,6 +950,49 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM searchContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) searchContainerNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationHeight, transition: transition) } + + if self.locationAccessDenied { + self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) + Queue.mainQueue().after(0.2) { + self.controller?.updateTabBarAlpha(0.0, .immediate) + } + + var placeholderTransition = transition + let placeholderNode: LocationPlaceholderNode + let backgroundNode: NavigationBackgroundNode + if let current = self.placeholderNode, let background = self.placeholderBackgroundNode { + placeholderNode = current + backgroundNode = background + + backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) + } else { + backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) + if let navigationBar = self.controller?.navigationBar { + self.insertSubnode(backgroundNode, belowSubnode: navigationBar) + } else { + self.addSubnode(backgroundNode) + } + self.placeholderBackgroundNode = backgroundNode + + placeholderNode = LocationPlaceholderNode(content: .intro) + placeholderNode.settingsPressed = { [weak self] in + self?.context.sharedContext.applicationBindings.openSettings() + } + self.insertSubnode(placeholderNode, aboveSubnode: backgroundNode) + self.placeholderNode = placeholderNode + + placeholderTransition = .immediate + } + placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, transition: placeholderTransition) + placeholderTransition.updateFrame(node: placeholderNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + let placeholderFrame = CGRect(origin: CGPoint(), size: layout.size) + backgroundNode.update(size: placeholderFrame.size, transition: placeholderTransition) + placeholderTransition.updateFrame(node: placeholderNode, frame: placeholderFrame) + } else if let placeholderNode = self.placeholderNode { + self.placeholderNode = nil + placeholderNode.removeFromSupernode() + } } func updateSendActionHighlight(_ highlighted: Bool) { diff --git a/submodules/LocationUI/Sources/LocationPlaceholderNode.swift b/submodules/LocationUI/Sources/LocationPlaceholderNode.swift new file mode 100644 index 0000000000..865d095b73 --- /dev/null +++ b/submodules/LocationUI/Sources/LocationPlaceholderNode.swift @@ -0,0 +1,134 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import SolidRoundedButtonNode +import PresentationDataUtils + +final class LocationPlaceholderNode: ASDisplayNode { + enum Content { + case intro + } + + private let content: Content + + private var animationNode: AnimatedStickerNode + private let titleNode: ImmediateTextNode + private let textNode: ImmediateTextNode + private let buttonNode: SolidRoundedButtonNode + private var validLayout: ContainerViewLayout? + + private var cameraTextNode: ImmediateTextNode + + var settingsPressed: () -> Void = {} + var cameraPressed: () -> Void = {} + + init(content: Content) { + self.content = content + + let name: String + let playbackMode: AnimatedStickerPlaybackMode + switch content { + case .intro: + name = "Location" + playbackMode = .loop + } + + self.animationNode = AnimatedStickerNode() + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: name), width: 320, height: 320, playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil)) + self.animationNode.visibility = true + + self.titleNode = ImmediateTextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.textAlignment = .center + self.titleNode.maximumNumberOfLines = 1 + + self.textNode = ImmediateTextNode() + self.textNode.isUserInteractionEnabled = false + self.textNode.lineSpacing = 0.1 + self.textNode.textAlignment = .center + self.textNode.maximumNumberOfLines = 0 + + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 12.0, gloss: true) + + self.cameraTextNode = ImmediateTextNode() + self.cameraTextNode.isUserInteractionEnabled = false + + super.init() + + self.addSubnode(self.animationNode) + self.addSubnode(self.textNode) + + if case .intro = self.content { + self.addSubnode(self.titleNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.pressed = { [weak self] in + self?.settingsPressed() + } + } + } + + private var theme: PresentationTheme? + func update(layout: ContainerViewLayout, theme: PresentationTheme, strings: PresentationStrings, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + let themeUpdated = self.theme != theme + self.theme = theme + + var imageSize = CGSize(width: 144.0, height: 144.0) + var insets = layout.insets(options: []) + if layout.size.width == 460.0 { + insets.top += -60.0 + imageSize = CGSize(width: 112.0, height: 112.0) + } else { + insets.top += -160.0 + } + + let imageSpacing: CGFloat = 12.0 + let textSpacing: CGFloat = 12.0 + let buttonSpacing: CGFloat = 15.0 + let cameraSpacing: CGFloat = 13.0 + + let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 + + if themeUpdated { + self.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: theme)) + } + self.buttonNode.title = strings.Attachment_OpenSettings + let buttonWidth: CGFloat = 248.0 + let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition) + + let title: String + let text: String + switch self.content { + case .intro: + title = strings.Attachment_LocationAccessTitle + text = strings.Attachment_LocationAccessText + } + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) + self.cameraTextNode.attributedText = NSAttributedString(string: strings.Attachment_OpenCamera, font: Font.regular(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center) + + let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let cameraSize = self.cameraTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 70.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + + let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height + buttonSpacing + buttonHeight + cameraSpacing + cameraSize.height + let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0) + + transition.updateAlpha(node: self.animationNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0) + transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize)) + self.animationNode.updateLayout(size: imageSize) + + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize)) + + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - buttonWidth - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: self.textNode.frame.maxY + buttonSpacing), size: CGSize(width: buttonWidth, height: buttonHeight))) + } +} + + diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift index 6d63017e7e..8908f9f630 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift @@ -120,6 +120,7 @@ private final class MediaGroupsGridAlbumItemNode : ListViewItemNode { self.imageNode = ImageNode() self.imageNode.clipsToBounds = true self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0)) + self.imageNode.contentMode = .scaleAspectFill self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false @@ -332,13 +333,19 @@ private class MediaGroupsAlbumGridItemNode: ListViewItemNode { let listInsets = UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0) strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width - params.leftInset - params.rightInset) strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0) - strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width - params.leftInset - params.rightInset), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) var entries: [MediaGroupsGridAlbumEntry] = [] var index: Int = 0 for collection in item.collections { let result = PHAsset.fetchAssets(in: collection, options: nil) - if let firstItem = result.firstObject { + let firstItem: PHAsset? + if collection.assetCollectionSubtype == .smartAlbumUserLibrary { + firstItem = result.lastObject + } else { + firstItem = result.firstObject + } + if let firstItem = firstItem { entries.append(MediaGroupsGridAlbumEntry(theme: item.presentationData.theme, index: index, collection: collection, firstItem: firstItem, count: presentationStringsFormattedNumber(Int32(result.count)))) index += 1 } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index a46ed9b0dd..8deae3cf55 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -18,6 +18,8 @@ import AttachmentUI import ContextUI import WebSearchUI +let overflowInset: CGFloat = 70.0 + final class MediaPickerInteraction { let openMedia: (PHFetchResult, Int, UIImage?) -> Void let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void @@ -281,6 +283,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.showsVerticalScrollIndicator = false + if self.controller?.collection != nil { + self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in + return point.x > 44.0 + overflowInset + } + } + if self.controller?.collection == nil { let cameraView = TGAttachmentCameraView(forSelfPortrait: false)! cameraView.clipsToBounds = true @@ -315,6 +323,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) } } + if self.controller?.collection != nil { + self.selectionGesture?.sideInset = 44.0 + overflowInset + } } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { @@ -324,7 +335,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return false } } - + fileprivate func dismissInput() { self.view.window?.endEditing(true) } @@ -611,13 +622,20 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private var previousContentOffset: GridNodeVisibleContentOffset? - func updateNavigation(transition: ContainedViewLayoutTransition) { + func updateNavigation(delayDisappear: Bool = false, transition: ContainedViewLayoutTransition) { if let selectionNode = self.selectionNode, selectionNode.alpha > 0.0 { self.controller?.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) self.controller?.updateTabBarAlpha(1.0, transition) } else if self.placeholderNode != nil { self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) - self.controller?.updateTabBarAlpha(0.0, transition) + + if delayDisappear { + Queue.mainQueue().after(0.2) { + self.controller?.updateTabBarAlpha(0.0, transition) + } + } else { + self.controller?.updateTabBarAlpha(0.0, transition) + } } else { var previousContentOffsetValue: CGFloat? if let previousContentOffset = self.previousContentOffset, case let .known(value) = previousContentOffset { @@ -637,6 +655,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { case .unknown, .none: self.controller?.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) } + self.controller?.updateTabBarAlpha(1.0, transition) } let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0) @@ -652,7 +671,6 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { var insets = layout.insets(options: []) insets.top += navigationBarHeight - let overflowInset: CGFloat = 70.0 let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)) let innerBounds = CGRect(origin: CGPoint(x: -overflowInset, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)) @@ -1039,9 +1057,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public func prepareForReuse() { self.controllerNode.cameraView?.resumePreview() - Queue.mainQueue().after(0.2, { - self.controllerNode.updateNavigation(transition: .animated(duration: 0.15, curve: .easeInOut)) - }) + self.controllerNode.updateNavigation(delayDisappear: true, transition: .immediate) } @objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { @@ -1051,12 +1067,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.presentWebSearch(MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in if let strongSelf = self { if let webSearchController = strongSelf.webSearchController { - strongSelf.webSearchController = nil if collection.assetCollectionSubtype != .smartAlbumUserLibrary { - Queue.mainQueue().after(0.5) { - webSearchController.cancel() - } +// strongSelf.webSearchController = nil +// Queue.mainQueue().after(0.5) { +// webSearchController.cancel() +// } } else { + strongSelf.webSearchController = nil webSearchController.cancel() } } @@ -1205,6 +1222,8 @@ private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer { private var initialLocation: CGPoint? + var sideInset: CGFloat = 0.0 + init(target: Any?, action: Selector?, gridNode: GridNode) { self.gridNode = gridNode @@ -1216,12 +1235,17 @@ private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer { override func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) - guard let touch = touches.first, let gridNode = self.gridNode else { + guard let touch = touches.first, self.numberOfTouches == 1, let gridNode = self.gridNode else { return } let location = touch.location(in: gridNode.view) - self.initialLocation = location + + if location.x > self.sideInset { + self.initialLocation = location + } else { + self.state = .failed + } } override func touchesMoved(_ touches: Set, with event: UIEvent) { diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift index 72c94adb4d..b9763d429e 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift @@ -189,7 +189,7 @@ public final class PermissionContentNode: ASDisplayNode { public func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { self.validLayout = (size, insets) - let sidePadding: CGFloat + var sidePadding: CGFloat let fontSize: CGFloat if min(size.width, size.height) > 330.0 { fontSize = 24.0 @@ -198,8 +198,9 @@ public final class PermissionContentNode: ASDisplayNode { fontSize = 20.0 sidePadding = 20.0 } + sidePadding += insets.left - let smallerSidePadding: CGFloat = 20.0 + let smallerSidePadding: CGFloat = 20.0 + insets.left self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(fontSize), textColor: self.theme.list.itemPrimaryTextColor) @@ -207,7 +208,7 @@ public final class PermissionContentNode: ASDisplayNode { let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude)) let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) let buttonInset: CGFloat = 16.0 - let buttonWidth = min(size.width, size.height) - buttonInset * 2.0 + let buttonWidth = min(size.width, size.height) - buttonInset * 2.0 - insets.left - insets.right let buttonHeight = self.actionButton.updateLayout(width: buttonWidth, transition: transition) let footerSize = self.footerNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude)) let privacyButtonSize = self.privacyPolicyButton.measure(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index a903ebcffe..aa980b9d50 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -169,6 +169,8 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } + var delayDisappear = false + var resetForReuseImpl: () -> Void = {} public func resetForReuse() { self.resetForReuseImpl() @@ -176,7 +178,9 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai } public func prepareForReuse() { + self.delayDisappear = true self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset) + self.delayDisappear = false } } @@ -300,13 +304,21 @@ public func attachmentFileController(context: AccountContext, updatedPresentatio } let controller = AttachmentFileControllerImpl(context: context, state: signal) + controller.delayDisappear = true controller.visibleBottomContentOffsetChanged = { [weak controller] offset in switch offset { case let .known(value): - let backgroundAlpha: CGFloat = min(30.0, value) / 30.0 - controller?.updateTabBarAlpha(backgroundAlpha, .immediate) + let backgroundAlpha: CGFloat = min(30.0, max(0.0, value)) / 30.0 + if backgroundAlpha.isZero && controller?.delayDisappear == true { + Queue.mainQueue().after(0.2, { + controller?.updateTabBarAlpha(backgroundAlpha, .animated(duration: 0.1, curve: .easeInOut)) + }) + } else { + controller?.updateTabBarAlpha(backgroundAlpha, .immediate) + } case .unknown, .none: controller?.updateTabBarAlpha(1.0, .immediate) + controller?.delayDisappear = false } } controller.resetForReuseImpl = { diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 801591f4d7..3baf874ab4 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -237,6 +237,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } self.displayNodeDidLoad() + + self.updateTabBarAlpha(1.0, .immediate) } override func viewWillAppear(_ animated: Bool) { @@ -342,6 +344,10 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController public var mediaPickerContext: AttachmentMediaPickerContext { return ContactsPickerContext(controller: self) } + + public func prepareForReuse() { + self.updateTabBarAlpha(1.0, .immediate) + } } private let searchBarFont = Font.regular(17.0) diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index 39af94a084..da0ebac425 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -496,7 +496,9 @@ class WebSearchControllerNode: ASDisplayNode { insets.top += segmentedHeight insets.bottom += toolbarHeight - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(layout)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + let gridInsets = UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: insets.bottom, right: layout.safeInsets.right) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: gridInsets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(layout)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)