mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
7f59789ffc
commit
0d12dd29ad
190 changed files with 16735 additions and 5317 deletions
BIN
Telegram/Telegram-iOS/Resources/Anvil.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Anvil.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/CraftFail.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/CraftFail.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/CraftFailOverlay.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/CraftFailOverlay.tgs
Normal file
Binary file not shown.
|
|
@ -185,16 +185,6 @@ public enum WallpaperUrlParameter {
|
|||
case gradient([UInt32], Int32?)
|
||||
}
|
||||
|
||||
public enum ResolvedUrlSettingsSection {
|
||||
case theme
|
||||
case devices
|
||||
case autoremoveMessages
|
||||
case twoStepAuth
|
||||
case enableLog
|
||||
case phonePrivacy
|
||||
case loginEmail
|
||||
}
|
||||
|
||||
public struct ResolvedBotChoosePeerTypes: OptionSet {
|
||||
public var rawValue: UInt32
|
||||
|
||||
|
|
@ -315,13 +305,12 @@ public enum ResolvedUrl {
|
|||
case share(url: String?, text: String?, to: String?)
|
||||
case wallpaper(WallpaperUrlParameter)
|
||||
case theme(String)
|
||||
case settings(ResolvedUrlSettingsSection)
|
||||
case joinVoiceChat(PeerId, String?)
|
||||
case importStickers
|
||||
case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?)
|
||||
case invoice(slug: String, invoice: TelegramMediaInvoice?)
|
||||
case premiumOffer(reference: String?)
|
||||
case starsTopup(amount: Int64, purpose: String?)
|
||||
case starsTopup(amount: Int64?, purpose: String?)
|
||||
case chatFolder(slug: String)
|
||||
case story(peerId: PeerId, id: Int32)
|
||||
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
|
||||
|
|
@ -336,6 +325,53 @@ public enum ResolvedUrl {
|
|||
case storyFolder(peerId: PeerId, id: Int64)
|
||||
case giftCollection(peerId: PeerId, id: Int64)
|
||||
case sendGift(peerId: PeerId?)
|
||||
case unknownDeepLink(path: String)
|
||||
case oauth(url: String)
|
||||
|
||||
public enum ChatsSection {
|
||||
case search
|
||||
case edit
|
||||
case emojiStatus
|
||||
}
|
||||
case chats(ChatsSection?)
|
||||
|
||||
public enum ComposeSection {
|
||||
case group
|
||||
case channel
|
||||
case contact
|
||||
}
|
||||
case compose(ComposeSection?)
|
||||
|
||||
public enum PostStorySection {
|
||||
case photo
|
||||
case video
|
||||
case live
|
||||
}
|
||||
case postStory(PostStorySection?)
|
||||
|
||||
public enum ContactsSection {
|
||||
case search
|
||||
case sort
|
||||
case new
|
||||
case invite
|
||||
case manage
|
||||
}
|
||||
case contacts(ContactsSection?)
|
||||
|
||||
public enum SettingsSection {
|
||||
public enum Legacy {
|
||||
case theme
|
||||
case devices
|
||||
case autoremoveMessages
|
||||
case enableLog
|
||||
case phonePrivacy
|
||||
case loginEmail
|
||||
}
|
||||
|
||||
case legacy(Legacy)
|
||||
case path(String)
|
||||
}
|
||||
case settings(SettingsSection)
|
||||
}
|
||||
|
||||
public enum ResolveUrlResult {
|
||||
|
|
@ -972,18 +1008,30 @@ public protocol MediaEditorScreenResult {
|
|||
var target: Stories.PendingTarget { get }
|
||||
}
|
||||
|
||||
public enum StoryCameraMode {
|
||||
case photo
|
||||
case video
|
||||
case live
|
||||
}
|
||||
|
||||
public protocol TelegramRootControllerInterface: NavigationController {
|
||||
@discardableResult
|
||||
func openStoryCamera(customTarget: Stories.PendingTarget?, resumeLiveStream: Bool, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
||||
func openStoryCamera(mode: StoryCameraMode, customTarget: Stories.PendingTarget?, resumeLiveStream: Bool, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
||||
func proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
|
||||
|
||||
func getContactsController() -> ViewController?
|
||||
func getChatsController() -> ViewController?
|
||||
func getSettingsController() -> ViewController?
|
||||
|
||||
func getPrivacySettings() -> Promise<AccountPrivacySettings?>?
|
||||
func openSettings()
|
||||
func getTwoStepAuthData() -> Promise<TwoStepAuthData?>?
|
||||
|
||||
func openContacts()
|
||||
func openSettings(edit: Bool)
|
||||
func openBirthdaySetup()
|
||||
func openPhotoSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?)
|
||||
func openAvatars()
|
||||
func startNewCall()
|
||||
}
|
||||
|
||||
public protocol QuickReplySetupScreenInitialData: AnyObject {
|
||||
|
|
@ -1185,6 +1233,7 @@ public enum ChannelMembersSearchControllerMode {
|
|||
case promote
|
||||
case ban
|
||||
case inviteToCall
|
||||
case ownershipTransfer
|
||||
}
|
||||
|
||||
public enum ChannelMembersSearchFilter {
|
||||
|
|
@ -1412,6 +1461,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
|
||||
func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, onlyCrafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController
|
||||
func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController
|
||||
func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController
|
||||
func makeGiftDemoScreen(context: AccountContext) -> ViewController
|
||||
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
|
||||
func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?)
|
||||
|
|
|
|||
|
|
@ -976,13 +976,19 @@ public enum PeerInfoAvatarUploadStatus {
|
|||
public protocol PeerInfoScreen: ViewController {
|
||||
var peerId: PeerId { get }
|
||||
var privacySettings: Promise<AccountPrivacySettings?> { get }
|
||||
var twoStepAuthData: Promise<TwoStepAuthData?> { get }
|
||||
|
||||
func activateEdit()
|
||||
func openEmojiStatusSetup()
|
||||
func openBirthdaySetup()
|
||||
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
||||
func togglePaneIsReordering(isReordering: Bool)
|
||||
func cancelItemSelection()
|
||||
func openAvatarSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?)
|
||||
func openAvatars()
|
||||
|
||||
func updateProfilePhoto(_ image: UIImage)
|
||||
func updateProfileVideo(_ image: UIImage, video: Any?, values: Any?, markup: UploadPeerPhotoMarkup?)
|
||||
}
|
||||
|
||||
public extension Peer {
|
||||
|
|
|
|||
|
|
@ -28,4 +28,7 @@ public protocol ChatListController: ViewController {
|
|||
func openStoriesFromNotification(peerId: EnginePeer.Id, storyId: Int32)
|
||||
|
||||
func resetForumStackIfOpen()
|
||||
|
||||
func activateEdit()
|
||||
func openEmojiStatusSetup()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -779,7 +779,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
self.ignoreUpdatesUntilScrollingStopped = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
|
||||
if #available(iOS 14.5, *), navigationAction.shouldPerformDownload {
|
||||
|
|
@ -798,11 +798,16 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
if let url = navigationAction.request.url?.absoluteString {
|
||||
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || url.hasPrefix("tg://")) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") {
|
||||
decisionHandler(.cancel, preferences)
|
||||
self.minimize()
|
||||
if !url.contains("domain=oauth") {
|
||||
self.minimize()
|
||||
}
|
||||
self.openAppUrl(url)
|
||||
} else {
|
||||
if let scheme = navigationAction.request.url?.scheme, !["http", "https", "tonsite", "about"].contains(scheme.lowercased()) {
|
||||
if let scheme = navigationAction.request.url?.scheme?.lowercased(), !["http", "https", "tonsite", "about"].contains(scheme) {
|
||||
decisionHandler(.cancel, preferences)
|
||||
if ["facetime"].contains(scheme) {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {})
|
||||
} else {
|
||||
decisionHandler(.allow, preferences)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,20 @@ public enum CallListControllerMode {
|
|||
case navigation
|
||||
}
|
||||
|
||||
public enum CallListEntryTag: ItemListItemTag, Equatable {
|
||||
case edit
|
||||
case showTab
|
||||
case missed
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? CallListEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DeleteAllButtonNode: ASDisplayNode {
|
||||
private let pressed: () -> Void
|
||||
|
||||
|
|
@ -69,6 +83,8 @@ private final class DeleteAllButtonNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public final class CallListController: TelegramBaseController {
|
||||
private var controllerNode: CallListControllerNode {
|
||||
return self.displayNode as! CallListControllerNode
|
||||
|
|
@ -81,6 +97,7 @@ public final class CallListController: TelegramBaseController {
|
|||
|
||||
private let context: AccountContext
|
||||
private let mode: CallListControllerMode
|
||||
private let focusOnItemTag: CallListEntryTag?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
|
@ -96,9 +113,15 @@ public final class CallListController: TelegramBaseController {
|
|||
private let clearDisposable = MetaDisposable()
|
||||
private var createConferenceCallDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, mode: CallListControllerMode) {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
mode: CallListControllerMode,
|
||||
focusOnItemTag: CallListEntryTag? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.segmentedTitleView = ItemListControllerSegmentedTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], selectedIndex: 0)
|
||||
|
|
@ -409,7 +432,7 @@ public final class CallListController: TelegramBaseController {
|
|||
if let strongSelf = self {
|
||||
strongSelf.callPressed()
|
||||
}
|
||||
})
|
||||
}, focusOnItemTag: self.focusOnItemTag)
|
||||
|
||||
if case .navigation = self.mode {
|
||||
self.controllerNode.navigationBar = self.navigationBar
|
||||
|
|
@ -421,6 +444,18 @@ public final class CallListController: TelegramBaseController {
|
|||
}
|
||||
self._ready.set(self.controllerNode.ready)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
switch self.focusOnItemTag {
|
||||
case .edit:
|
||||
self.editPressed()
|
||||
case .missed:
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.segmentedTitleView.index = 1
|
||||
self.controllerNode.updateType(.missed)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override public var navigationEdgeEffectExtension: CGFloat {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
|||
case let .displayTab(_, text, value):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
|
||||
nodeInteraction.updateShowCallsTab(value)
|
||||
}), directionHint: entry.directionHint)
|
||||
}, tag: CallListEntryTag.showTab), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
||||
case .openNewCall:
|
||||
|
|
@ -149,7 +149,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
|||
case let .displayTab(_, text, value):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
|
||||
nodeInteraction.updateShowCallsTab(value)
|
||||
}), directionHint: entry.directionHint)
|
||||
}, tag: CallListEntryTag.showTab), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
||||
case .openNewCall:
|
||||
|
|
@ -184,6 +184,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||
private let context: AccountContext
|
||||
private let mode: CallListControllerMode
|
||||
private var presentationData: PresentationData
|
||||
private var focusOnItemTag: CallListEntryTag?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
|
|
@ -241,7 +242,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||
|
||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||
|
||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, openNewCall: @escaping () -> Void) {
|
||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, openNewCall: @escaping () -> Void, focusOnItemTag: CallListEntryTag?) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
|
|
@ -253,6 +254,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||
self.openNewCall = openNewCall
|
||||
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
|
|
@ -861,6 +863,16 @@ final class CallListControllerNode: ASDisplayNode {
|
|||
strongSelf._ready.set(true)
|
||||
}
|
||||
|
||||
if let focusOnItemTag = strongSelf.focusOnItemTag {
|
||||
strongSelf.focusOnItemTag = nil
|
||||
|
||||
strongSelf.listNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent",
|
||||
"//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode",
|
||||
"//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent",
|
||||
"//submodules/TelegramUI/Components/AlertComponent",
|
||||
"//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent",
|
||||
"//submodules/TelegramUI/Components/AvatarComponent",
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
private var didSuggestLoginEmailSetup = false
|
||||
private var didSuggestLoginPasskeySetup = false
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private(set) var presentationData: PresentationData
|
||||
private let presentationDataValue = Promise<PresentationData>()
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
|
|
@ -3029,7 +3029,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
|
||||
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||
let coordinator = rootController.openStoryCamera(customTarget: nil, resumeLiveStream: hasLiveStream, transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
|
||||
let coordinator = rootController.openStoryCamera(mode: .photo, customTarget: nil, resumeLiveStream: hasLiveStream, transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
|
||||
coordinator?.animateIn()
|
||||
}
|
||||
}
|
||||
|
|
@ -3511,7 +3511,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
super.navigationStackConfigurationUpdated(next: next)
|
||||
}
|
||||
|
||||
@objc fileprivate func editPressed() {
|
||||
public func activateEdit() {
|
||||
self.editPressed()
|
||||
}
|
||||
|
||||
public func openEmojiStatusSetup() {
|
||||
if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View {
|
||||
navigationBarView.openEmojiStatusSetup()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func editPressed() {
|
||||
if self.secondaryContext == nil {
|
||||
if case .chatList(.root) = self.chatListDisplayNode.effectiveContainerNode.location {
|
||||
self.effectiveContext?.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
|
||||
|
|
@ -5874,10 +5884,33 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
})
|
||||
], parseMarkdown: true), in: .window(.root))
|
||||
} else {
|
||||
completion(true)
|
||||
self.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
|
||||
removed()
|
||||
})
|
||||
let proceed = {
|
||||
completion(true)
|
||||
self.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
|
||||
removed()
|
||||
})
|
||||
}
|
||||
if case let .channel(channel) = peer.peer, channel.flags.contains(.isCreator) {
|
||||
let _ = (self.context.engine.peers.getFutureCreatorAfterLeave(peerId: channel.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] nextCreator in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let nextCreator, let peer = peer.peer {
|
||||
self.presentLeaveChannelConfirmation(peer: peer, nextCreator: nextCreator, completion: { commit in
|
||||
if commit {
|
||||
proceed()
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6425,7 +6458,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
if let current = self.storyCameraTransitionInCoordinator {
|
||||
coordinator = current
|
||||
} else {
|
||||
coordinator = rootController.openStoryCamera(customTarget: nil, resumeLiveStream: false, transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target, _ in
|
||||
coordinator = rootController.openStoryCamera(mode: .photo, customTarget: nil, resumeLiveStream: false, transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target, _ in
|
||||
guard let self, let target else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
import Foundation
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import AlertComponent
|
||||
import AlertTransferHeaderComponent
|
||||
import AvatarComponent
|
||||
import PeerInfoUI
|
||||
import OwnershipTransferController
|
||||
|
||||
extension ChatListControllerImpl {
|
||||
func presentLeaveChannelConfirmation(peer: EnginePeer, nextCreator: EnginePeer, completion: @escaping (Bool) -> Void) {
|
||||
Task { @MainActor in
|
||||
let accountPeer = await (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))).get()
|
||||
|
||||
guard let accountPeer else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "header",
|
||||
component: AnyComponent(
|
||||
AlertTransferHeaderComponent(
|
||||
fromComponent: AnyComponentWithIdentity(id: "account", component: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
peer: accountPeer
|
||||
)
|
||||
)),
|
||||
toComponent: AnyComponentWithIdentity(id: "user", component: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
peer: nextCreator,
|
||||
icon: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
peer: peer
|
||||
)
|
||||
)
|
||||
)
|
||||
)),
|
||||
type: .transfer
|
||||
)
|
||||
)
|
||||
))
|
||||
//TODO:localize
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: "Leave \(peer.compactDisplayTitle)")
|
||||
)
|
||||
))
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain("If you leave, **\(nextCreator.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))** will become the owner of **\(peer.compactDisplayTitle)** in **1 week**."))
|
||||
)
|
||||
))
|
||||
|
||||
let alertController = AlertScreen(
|
||||
context: self.context,
|
||||
configuration: .init(actionAlignment: .vertical),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: "Appoint Another Owner", action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentOwnershipTransfer(chatPeer: peer)
|
||||
}),
|
||||
.init(title: "Cancel", action: {
|
||||
completion(false)
|
||||
}),
|
||||
.init(title: "Leave Group", type: .destructive, action: {
|
||||
completion(true)
|
||||
})
|
||||
]
|
||||
)
|
||||
if let topController = self.navigationController?.topViewController as? ViewController {
|
||||
topController.present(alertController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func presentOwnershipTransfer(chatPeer: EnginePeer) {
|
||||
let presentController: (ViewController) -> Void = { [weak self] c in
|
||||
if let topController = self?.navigationController?.topViewController as? ViewController {
|
||||
topController.present(c, in: .window(.root))
|
||||
}
|
||||
}
|
||||
let pushController: (ViewController) -> Void = { [weak self] c in
|
||||
if let topController = self?.navigationController?.topViewController as? ViewController {
|
||||
topController.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
var dismissController: (() -> Void)?
|
||||
let controller = ChannelMembersSearchControllerImpl(
|
||||
params: ChannelMembersSearchControllerParams(
|
||||
context: self.context,
|
||||
peerId: chatPeer.id,
|
||||
mode: .ownershipTransfer,
|
||||
filters: [],
|
||||
openPeer: { [weak self] peer, participant in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if peer.id == self.context.account.peerId {
|
||||
return
|
||||
}
|
||||
if let participant {
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
return
|
||||
case let .member(_, _, adminInfo, _, _, _):
|
||||
let _ = adminInfo
|
||||
|
||||
let _ = (self.context.engine.peers.checkOwnershipTranfserAvailability(memberId: peer.id) |> deliverOnMainQueue).start(error: { [weak self] error in
|
||||
guard let self, case let .user(user) = peer else {
|
||||
return
|
||||
}
|
||||
let controller = channelOwnershipTransferController(
|
||||
context: self.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: chatPeer,
|
||||
member: user,
|
||||
initialError: error,
|
||||
present: { c, a in
|
||||
presentController(c)
|
||||
},
|
||||
push: { c in
|
||||
pushController(c)
|
||||
},
|
||||
completion: { _ in
|
||||
dismissController?()
|
||||
}
|
||||
)
|
||||
presentController(controller)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
dismissController = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
pushController(controller)
|
||||
}
|
||||
}
|
||||
|
|
@ -1388,7 +1388,7 @@ private final class ChatListFilterPresetController: ItemListController {
|
|||
}
|
||||
}
|
||||
|
||||
func chatListFilterPresetController(context: AccountContext, currentPreset initialPreset: ChatListFilter?, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
|
||||
public func chatListFilterPresetController(context: AccountContext, currentPreset initialPreset: ChatListFilter?, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
|
||||
let initialName: ChatFolderTitle
|
||||
if let initialPreset {
|
||||
initialName = initialPreset.title
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ private enum ChatListFilterPresetListSection: Int32 {
|
|||
}
|
||||
|
||||
public enum ChatListFilterPresetListEntryTag: ItemListItemTag {
|
||||
case edit
|
||||
case addRecommended
|
||||
case displayTags
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
|
|
@ -175,7 +177,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
|||
case let .screenHeader(text):
|
||||
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animation: .folders, sectionId: self.section)
|
||||
case let .suggestedListHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section, tag: ChatListFilterPresetListEntryTag.addRecommended)
|
||||
case let .suggestedPreset(_, title, label, preset):
|
||||
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, systemStyle: .glass, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: {
|
||||
arguments.addSuggestedPressed(title, preset)
|
||||
|
|
@ -318,7 +320,7 @@ public enum ChatListFilterPresetListControllerMode {
|
|||
case modal
|
||||
}
|
||||
|
||||
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, scrollToTags: Bool = false, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, scrollToTags: Bool = false, focusOnItemTag: ChatListFilterPresetListEntryTag? = nil, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
let initialState = ChatListFilterPresetListControllerState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
|
|
@ -326,6 +328,14 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
if focusOnItemTag == .edit {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.isEditing = true
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
|
|
@ -680,7 +690,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatListFolderSettings_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let entries = chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, initialScrollToItem: scrollToTags ? ListViewScrollToItem(index: entries.count - 1, position: .center(.bottom), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down) : nil, animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToTags ? ListViewScrollToItem(index: entries.count - 1, position: .center(.bottom), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down) : nil, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -797,6 +807,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
guard let toggleDirection = animateNextShowHideTagsTransition.swap(nil) else {
|
||||
return
|
||||
|
|
@ -819,6 +831,15 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||
}
|
||||
delay += 0.02
|
||||
}
|
||||
|
||||
if let focusOnItemTag, !didFocusOnItem {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public final class FilledRoundedRectangleComponent: Component {
|
|||
}
|
||||
} else {
|
||||
if component.smoothCorners {
|
||||
let size = CGSize(width: cornerRadius * 2.0 + 10.0, height: cornerRadius * 2.0 + 10.0)
|
||||
let size = CGSize(width: cornerRadius * 2.0 + 8.0, height: cornerRadius * 2.0 + 8.0)
|
||||
if let cornerImage = self.cornerImage, cornerImage.size == size {
|
||||
} else {
|
||||
self.cornerImage = generateImage(size, rotatedContext: { size, context in
|
||||
|
|
|
|||
23
submodules/Components/ResizableSheetComponent/BUILD
Normal file
23
submodules/Components/ResizableSheetComponent/BUILD
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ResizableSheetComponent",
|
||||
module_name = "ResizableSheetComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,678 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import ViewControllerComponent
|
||||
import SwiftSignalKit
|
||||
import DynamicCornerRadiusView
|
||||
import TelegramPresentationData
|
||||
import EdgeEffect
|
||||
|
||||
public final class ResizableSheetComponentEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
public let statusBarHeight: CGFloat
|
||||
public let safeInsets: UIEdgeInsets
|
||||
public let metrics: LayoutMetrics
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let isDisplaying: Bool
|
||||
public let isCentered: Bool
|
||||
public let screenSize: CGSize
|
||||
public let regularMetricsSize: CGSize?
|
||||
public let dismiss: (Bool) -> Void
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
statusBarHeight: CGFloat,
|
||||
safeInsets: UIEdgeInsets,
|
||||
metrics: LayoutMetrics,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
isDisplaying: Bool,
|
||||
isCentered: Bool,
|
||||
screenSize: CGSize,
|
||||
regularMetricsSize: CGSize?,
|
||||
dismiss: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.statusBarHeight = statusBarHeight
|
||||
self.safeInsets = safeInsets
|
||||
self.metrics = metrics
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.isDisplaying = isDisplaying
|
||||
self.isCentered = isCentered
|
||||
self.screenSize = screenSize
|
||||
self.regularMetricsSize = regularMetricsSize
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
public static func ==(lhs: ResizableSheetComponentEnvironment, rhs: ResizableSheetComponentEnvironment) -> Bool {
|
||||
if lhs.theme != rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.statusBarHeight != rhs.statusBarHeight {
|
||||
return false
|
||||
}
|
||||
if lhs.safeInsets != rhs.safeInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.metrics != rhs.metrics {
|
||||
return false
|
||||
}
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
if lhs.isDisplaying != rhs.isDisplaying {
|
||||
return false
|
||||
}
|
||||
if lhs.isCentered != rhs.isCentered {
|
||||
return false
|
||||
}
|
||||
if lhs.screenSize != rhs.screenSize {
|
||||
return false
|
||||
}
|
||||
if lhs.regularMetricsSize != rhs.regularMetricsSize {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equatable>: Component {
|
||||
public typealias EnvironmentType = (ChildEnvironmentType, ResizableSheetComponentEnvironment)
|
||||
|
||||
public class ExternalState {
|
||||
public fileprivate(set) var contentHeight: CGFloat
|
||||
|
||||
public init() {
|
||||
self.contentHeight = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
public enum BackgroundColor: Equatable {
|
||||
case color(UIColor)
|
||||
}
|
||||
|
||||
public let content: AnyComponent<ChildEnvironmentType>
|
||||
public let titleItem: AnyComponent<Empty>?
|
||||
public let leftItem: AnyComponent<Empty>?
|
||||
public let rightItem: AnyComponent<Empty>?
|
||||
public let hasTopEdgeEffect: Bool
|
||||
public let bottomItem: AnyComponent<Empty>?
|
||||
public let backgroundColor: BackgroundColor
|
||||
public let isFullscreen: Bool
|
||||
public let externalState: ExternalState?
|
||||
public let animateOut: ActionSlot<Action<()>>
|
||||
|
||||
public init(
|
||||
content: AnyComponent<ChildEnvironmentType>,
|
||||
titleItem: AnyComponent<Empty>? = nil,
|
||||
leftItem: AnyComponent<Empty>? = nil,
|
||||
rightItem: AnyComponent<Empty>? = nil,
|
||||
hasTopEdgeEffect: Bool = true,
|
||||
bottomItem: AnyComponent<Empty>? = nil,
|
||||
backgroundColor: BackgroundColor,
|
||||
isFullscreen: Bool = false,
|
||||
externalState: ExternalState? = nil,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
) {
|
||||
self.content = content
|
||||
self.titleItem = titleItem
|
||||
self.leftItem = leftItem
|
||||
self.rightItem = rightItem
|
||||
self.hasTopEdgeEffect = hasTopEdgeEffect
|
||||
self.bottomItem = bottomItem
|
||||
self.backgroundColor = backgroundColor
|
||||
self.isFullscreen = isFullscreen
|
||||
self.externalState = externalState
|
||||
self.animateOut = animateOut
|
||||
}
|
||||
|
||||
public static func ==(lhs: ResizableSheetComponent, rhs: ResizableSheetComponent) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.titleItem != rhs.titleItem {
|
||||
return false
|
||||
}
|
||||
if lhs.leftItem != rhs.leftItem {
|
||||
return false
|
||||
}
|
||||
if lhs.rightItem != rhs.rightItem {
|
||||
return false
|
||||
}
|
||||
if lhs.hasTopEdgeEffect != rhs.hasTopEdgeEffect {
|
||||
return false
|
||||
}
|
||||
if lhs.bottomItem != rhs.bottomItem {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.isFullscreen != rhs.isFullscreen {
|
||||
return false
|
||||
}
|
||||
if lhs.animateOut != rhs.animateOut {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private struct ItemLayout: Equatable {
|
||||
var containerSize: CGSize
|
||||
var containerInset: CGFloat
|
||||
var containerCornerRadius: CGFloat
|
||||
var bottomInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
|
||||
init(containerSize: CGSize, containerInset: CGFloat, containerCornerRadius: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
|
||||
self.containerSize = containerSize
|
||||
self.containerInset = containerInset
|
||||
self.containerCornerRadius = containerCornerRadius
|
||||
self.bottomInset = bottomInset
|
||||
self.topInset = topInset
|
||||
}
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView {
|
||||
public final class Tag {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let _ = tag as? Tag {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private let dimView: UIView
|
||||
private let containerView: UIView
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let navigationBarContainer: SparseContainerView
|
||||
private let bottomContainer: SparseContainerView
|
||||
private let scrollView: ScrollView
|
||||
private let scrollContentClippingView: SparseContainerView
|
||||
private let scrollContentView: UIView
|
||||
|
||||
private let topEdgeEffectView: EdgeEffectView
|
||||
private let bottomEdgeEffectView: EdgeEffectView
|
||||
private let contentView: ComponentView<ChildEnvironmentType>
|
||||
|
||||
private var titleItemView: ComponentView<Empty>?
|
||||
private var leftItemView: ComponentView<Empty>?
|
||||
private var rightItemView: ComponentView<Empty>?
|
||||
private var bottomItemView: ComponentView<Empty>?
|
||||
|
||||
private let backgroundHandleView: UIImageView
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var component: ResizableSheetComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
private var environment: ResizableSheetComponentEnvironment?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.dimView = UIView()
|
||||
self.containerView = UIView()
|
||||
|
||||
self.containerView.clipsToBounds = true
|
||||
self.containerView.layer.cornerRadius = 40.0
|
||||
self.containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.backgroundLayer.cornerRadius = 40.0
|
||||
|
||||
self.backgroundHandleView = UIImageView()
|
||||
|
||||
self.navigationBarContainer = SparseContainerView()
|
||||
self.bottomContainer = SparseContainerView()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
|
||||
self.scrollContentClippingView = SparseContainerView()
|
||||
self.scrollContentClippingView.clipsToBounds = true
|
||||
|
||||
self.scrollContentView = UIView()
|
||||
|
||||
self.topEdgeEffectView = EdgeEffectView()
|
||||
self.topEdgeEffectView.clipsToBounds = true
|
||||
self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.topEdgeEffectView.layer.cornerRadius = 40.0
|
||||
|
||||
self.bottomEdgeEffectView = EdgeEffectView()
|
||||
self.bottomEdgeEffectView.clipsToBounds = true
|
||||
self.bottomEdgeEffectView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
self.bottomEdgeEffectView.layer.cornerRadius = 40.0
|
||||
|
||||
self.contentView = ComponentView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
self.addSubview(self.containerView)
|
||||
self.containerView.layer.addSublayer(self.backgroundLayer)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = true
|
||||
|
||||
self.containerView.addSubview(self.scrollContentClippingView)
|
||||
self.scrollContentClippingView.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.scrollContentView)
|
||||
|
||||
self.containerView.addSubview(self.navigationBarContainer)
|
||||
self.containerView.addSubview(self.bottomContainer)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if !self.backgroundLayer.frame.contains(point) {
|
||||
return self.dimView
|
||||
}
|
||||
|
||||
if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) {
|
||||
return result
|
||||
}
|
||||
if let result = self.bottomContainer.hitTest(self.convert(point, to: self.bottomContainer), with: event) {
|
||||
return result
|
||||
}
|
||||
let result = super.hitTest(point, with: event)
|
||||
return result
|
||||
}
|
||||
|
||||
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.dismissAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
guard let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
self.endEditing(true)
|
||||
environment.dismiss(true)
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: ComponentTransition) {
|
||||
guard let itemLayout = self.itemLayout, let component = self.component else {
|
||||
return
|
||||
}
|
||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||
topOffset = max(0.0, topOffset)
|
||||
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||
|
||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||
|
||||
var topOffsetFraction = self.scrollView.bounds.minY / 100.0
|
||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||
|
||||
if component.isFullscreen {
|
||||
topOffsetFraction = 1.0
|
||||
}
|
||||
|
||||
let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width
|
||||
let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0
|
||||
let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius
|
||||
|
||||
let scale = minScale * (1.0 - topOffsetFraction) + 1.0 * topOffsetFraction
|
||||
let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction)
|
||||
let scaledCornerRadius = minScaledCornerRadius * (1.0 - topOffsetFraction) + itemLayout.containerCornerRadius * topOffsetFraction
|
||||
|
||||
var containerTransform = CATransform3DIdentity
|
||||
containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0)
|
||||
containerTransform = CATransform3DScale(containerTransform, scale, scale, scale)
|
||||
transition.setTransform(view: self.containerView, transform: containerTransform)
|
||||
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius)
|
||||
|
||||
if component.isFullscreen {
|
||||
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: .zero, size: self.scrollView.bounds.size))
|
||||
self.scrollView.isScrollEnabled = false
|
||||
} else {
|
||||
self.scrollView.isScrollEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
private var didPlayAppearanceAnimation = false
|
||||
func animateIn() {
|
||||
self.didPlayAppearanceAnimation = true
|
||||
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.bottomContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
|
||||
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.bottomContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
func update(component: ResizableSheetComponent<ChildEnvironmentType>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let sheetEnvironment = environment[ResizableSheetComponentEnvironment.self].value
|
||||
component.animateOut.connect { [weak self] completion in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.animateOut {
|
||||
completion(Void())
|
||||
}
|
||||
}
|
||||
|
||||
let resetScrolling = self.scrollView.bounds.width != availableSize.width
|
||||
|
||||
let fillingSize: CGFloat
|
||||
if case .regular = sheetEnvironment.metrics.widthClass {
|
||||
fillingSize = min(availableSize.width, 414.0) - sheetEnvironment.safeInsets.left * 2.0
|
||||
} else {
|
||||
fillingSize = min(availableSize.width, sheetEnvironment.deviceMetrics.screenSize.width) - sheetEnvironment.safeInsets.left * 2.0
|
||||
}
|
||||
let rawSideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5)
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
self.environment = sheetEnvironment
|
||||
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
let backgroundColor: UIColor
|
||||
switch component.backgroundColor {
|
||||
case let .color(color):
|
||||
backgroundColor = color
|
||||
self.backgroundLayer.backgroundColor = backgroundColor.cgColor
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
var containerSize: CGSize
|
||||
if !"".isEmpty, sheetEnvironment.isCentered {
|
||||
let verticalInset: CGFloat = 44.0
|
||||
let maxSide = max(availableSize.width, availableSize.height)
|
||||
let minSide = min(availableSize.width, availableSize.height)
|
||||
containerSize = CGSize(width: min(availableSize.width - 20.0, floor(maxSide / 2.0)), height: min(availableSize.height, minSide) - verticalInset * 2.0)
|
||||
if let regularMetricsSize = sheetEnvironment.regularMetricsSize {
|
||||
containerSize = regularMetricsSize
|
||||
}
|
||||
} else {
|
||||
containerSize = CGSize(width: fillingSize, height: .greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
var containerInset: CGFloat = sheetEnvironment.statusBarHeight + 10.0
|
||||
if component.isFullscreen {
|
||||
containerInset = 0.0
|
||||
}
|
||||
let clippingY: CGFloat
|
||||
|
||||
self.contentView.parentState = state
|
||||
let contentViewSize = self.contentView.update(
|
||||
transition: transition,
|
||||
component: component.content,
|
||||
environment: {
|
||||
environment[ChildEnvironmentType.self]
|
||||
},
|
||||
containerSize: containerSize
|
||||
)
|
||||
component.externalState?.contentHeight = contentViewSize.height
|
||||
|
||||
if let contentView = self.contentView.view {
|
||||
if contentView.superview == nil {
|
||||
self.scrollContentView.addSubview(contentView)
|
||||
}
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: contentViewSize))
|
||||
}
|
||||
|
||||
let contentHeight = contentViewSize.height
|
||||
let initialContentHeight = contentHeight
|
||||
|
||||
let edgeEffectHeight: CGFloat = 80.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.topEdgeEffectView.update(content: backgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
if self.topEdgeEffectView.superview == nil {
|
||||
self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0)
|
||||
}
|
||||
self.topEdgeEffectView.isHidden = !component.hasTopEdgeEffect
|
||||
|
||||
if let titleItem = component.titleItem {
|
||||
let titleItemView: ComponentView<Empty>
|
||||
if let current = self.titleItemView {
|
||||
titleItemView = current
|
||||
} else {
|
||||
titleItemView = ComponentView<Empty>()
|
||||
self.titleItemView = titleItemView
|
||||
}
|
||||
|
||||
let titleItemSize = titleItemView.update(
|
||||
transition: transition,
|
||||
component: titleItem,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerSize.width - 66.0 * 2.0, height: 66.0)
|
||||
)
|
||||
let titleItemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerSize.width - titleItemSize.width)) / 2.0, y: floorToScreenPixels(36.0 - titleItemSize.height * 0.5)), size: titleItemSize)
|
||||
if let view = titleItemView.view {
|
||||
if view.superview == nil {
|
||||
self.navigationBarContainer.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: titleItemFrame)
|
||||
}
|
||||
} else if let titleItemView = self.titleItemView {
|
||||
self.titleItemView = nil
|
||||
titleItemView.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
|
||||
if let leftItem = component.leftItem {
|
||||
var leftItemTransition = transition
|
||||
let leftItemView: ComponentView<Empty>
|
||||
if let current = self.leftItemView {
|
||||
leftItemView = current
|
||||
} else {
|
||||
leftItemTransition = .immediate
|
||||
leftItemView = ComponentView<Empty>()
|
||||
self.leftItemView = leftItemView
|
||||
}
|
||||
|
||||
let leftItemSize = leftItemView.update(
|
||||
transition: leftItemTransition,
|
||||
component: leftItem,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 66.0, height: 66.0)
|
||||
)
|
||||
let leftItemFrame = CGRect(origin: CGPoint(x: rawSideInset + 16.0, y: 16.0), size: leftItemSize)
|
||||
if let view = leftItemView.view {
|
||||
if view.superview == nil {
|
||||
self.navigationBarContainer.addSubview(view)
|
||||
}
|
||||
leftItemTransition.setFrame(view: view, frame: leftItemFrame)
|
||||
}
|
||||
} else if let leftItemView = self.leftItemView {
|
||||
self.leftItemView = nil
|
||||
if !transition.animation.isImmediate {
|
||||
leftItemView.view?.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
leftItemView.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
leftItemView.view?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
leftItemView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let rightItem = component.rightItem {
|
||||
var rightItemTransition = transition
|
||||
let rightItemView: ComponentView<Empty>
|
||||
if let current = self.rightItemView {
|
||||
rightItemView = current
|
||||
} else {
|
||||
rightItemTransition = .immediate
|
||||
rightItemView = ComponentView<Empty>()
|
||||
self.rightItemView = rightItemView
|
||||
}
|
||||
|
||||
let rightItemSize = rightItemView.update(
|
||||
transition: rightItemTransition,
|
||||
component: rightItem,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 66.0, height: 66.0)
|
||||
)
|
||||
let rightItemFrame = CGRect(origin: CGPoint(x: availableSize.width - rawSideInset - 16.0 - rightItemSize.width, y: 16.0), size: rightItemSize)
|
||||
if let view = rightItemView.view {
|
||||
if view.superview == nil {
|
||||
self.navigationBarContainer.addSubview(view)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
rightItemTransition.setFrame(view: view, frame: rightItemFrame)
|
||||
}
|
||||
} else if let rightItemView = self.rightItemView {
|
||||
self.rightItemView = nil
|
||||
if !transition.animation.isImmediate {
|
||||
rightItemView.view?.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
rightItemView.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
rightItemView.view?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
rightItemView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let bottomItem = component.bottomItem {
|
||||
let bottomItemView: ComponentView<Empty>
|
||||
if let current = self.bottomItemView {
|
||||
bottomItemView = current
|
||||
} else {
|
||||
bottomItemView = ComponentView<Empty>()
|
||||
self.bottomItemView = bottomItemView
|
||||
}
|
||||
|
||||
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: sheetEnvironment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let bottomItemSize = bottomItemView.update(
|
||||
transition: transition,
|
||||
component: bottomItem,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerSize.width - bottomInsets.left - bottomInsets.right, height: 52.0)
|
||||
)
|
||||
let bottomItemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerSize.width - bottomItemSize.width)) / 2.0, y: availableSize.height - bottomItemSize.height - bottomInsets.bottom), size: bottomItemSize)
|
||||
if let view = bottomItemView.view {
|
||||
if view.superview == nil {
|
||||
self.bottomContainer.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: bottomItemFrame)
|
||||
}
|
||||
} else if let bottomItemView = self.bottomItemView {
|
||||
self.bottomItemView = nil
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: availableSize.height - edgeEffectHeight), size: CGSize(width: fillingSize, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
|
||||
self.bottomEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition)
|
||||
if self.bottomEdgeEffectView.superview == nil {
|
||||
self.bottomContainer.insertSubview(self.bottomEdgeEffectView, at: 0)
|
||||
}
|
||||
transition.setAlpha(view: self.bottomContainer, alpha: component.bottomItem != nil ? 1.0 : 0.0)
|
||||
|
||||
|
||||
clippingY = availableSize.height
|
||||
|
||||
var topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight)
|
||||
if component.isFullscreen {
|
||||
topInset = 0.0
|
||||
}
|
||||
|
||||
let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset)
|
||||
|
||||
self.scrollContentClippingView.layer.cornerRadius = 38.0
|
||||
|
||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, containerCornerRadius: sheetEnvironment.deviceMetrics.screenCornerRadius, bottomInset: sheetEnvironment.safeInsets.bottom, topInset: topInset)
|
||||
|
||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height)))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||
|
||||
self.ignoreScrolling = true
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||
let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight)
|
||||
if contentSize != self.scrollView.contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
if resetScrolling {
|
||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize)
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
transition.setPosition(view: self.containerView, position: CGRect(origin: CGPoint(), size: availableSize).center)
|
||||
transition.setBounds(view: self.containerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
if sheetEnvironment.isDisplaying && !self.didPlayAppearanceAnimation {
|
||||
self.animateIn()
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -575,7 +575,7 @@ public class ContactsController: ViewController {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func sortPressed() {
|
||||
@objc public func sortPressed() {
|
||||
self.sortButton.contextAction?(self.sortButton.containerNode, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ private final class ContactContextExtractedContentSource: ContextExtractedConten
|
|||
}
|
||||
}
|
||||
|
||||
private func presentContactAccessPicker(context: AccountContext) {
|
||||
public func presentContactAccessPicker(context: AccountContext) {
|
||||
if #available(iOS 18.0, *), let rootViewController = context.sharedContext.mainWindow?.viewController?.view.window?.rootViewController {
|
||||
var dismissImpl: (() -> Void)?
|
||||
let pickerView = ContactAccessPickerHostingView(completionHandler: { [weak rootViewController] ids in
|
||||
|
|
|
|||
|
|
@ -835,6 +835,17 @@ private func makeLayerSubtreeSnapshotAsView(layer: CALayer) -> UIView? {
|
|||
}
|
||||
|
||||
|
||||
public func findParentScrollView(view: UIView?) -> UIScrollView? {
|
||||
if let view = view {
|
||||
if let view = view as? UIScrollView {
|
||||
return view
|
||||
}
|
||||
return findParentScrollView(view: view.superview)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
func snapshotContentTree(unhide: Bool = false, keepPortals: Bool = false, keepTransform: Bool = false) -> UIView? {
|
||||
let wasHidden = self.isHidden
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
|||
let noInsets: Bool
|
||||
public let sectionId: ItemListSectionId
|
||||
public let action: (() -> Void)?
|
||||
public let tag: Any?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .blocks, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage?, iconSignal: Signal<UIImage?, NoError>? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?) {
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .blocks, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage?, iconSignal: Signal<UIImage?, NoError>? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?, tag: Any? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.style = style
|
||||
self.systemStyle = systemStyle
|
||||
|
|
@ -52,6 +53,7 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
|||
self.color = color
|
||||
self.sectionId = sectionId
|
||||
self.action = action
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
|
|
@ -133,6 +135,10 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
|
|||
|
||||
private let iconDisposable = MetaDisposable()
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag as? ItemListItemTag
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ public extension ItemListItem {
|
|||
|
||||
public protocol ItemListItemNode {
|
||||
var tag: ItemListItemTag? { get }
|
||||
|
||||
func displayHighlight()
|
||||
}
|
||||
|
||||
public extension ItemListItemNode {
|
||||
func displayHighlight() {
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ItemListItemFocusableNode {
|
||||
|
|
|
|||
|
|
@ -42,8 +42,9 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
|||
public let sectionId: ItemListSectionId
|
||||
let action: () -> Void
|
||||
let deleteAction: (() -> Void)?
|
||||
public let tag: Any?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, enabled: Bool = true, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, enabled: Bool = true, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil, tag: Any? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.systemStyle = systemStyle
|
||||
self.icon = icon
|
||||
|
|
@ -60,6 +61,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
|||
self.sectionId = sectionId
|
||||
self.action = action
|
||||
self.deleteAction = deleteAction
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
|
|
@ -127,6 +129,10 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||
return self.contentParentNode
|
||||
}
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag as? ItemListItemTag
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ private let boldBadgeFont = Font.semibold(14.0)
|
|||
|
||||
public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
|
@ -215,6 +216,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.isUserInteractionEnabled = false
|
||||
|
||||
|
|
@ -283,6 +287,20 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
transition.updateAlpha(node: self.arrowNode, alpha: hasContextMenu ? 0.5 : 1.0)
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode.textNode)
|
||||
let makeTitleWithEntitiesLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
|
||||
|
|
@ -600,6 +618,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.highlightNode.backgroundColor = item.presentationData.theme.list.itemSearchHighlightColor
|
||||
}
|
||||
|
||||
if let titleWithEntitiesApply = titleWithEntitiesLayoutAndApply?.1, let context = item.context {
|
||||
|
|
@ -671,6 +690,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
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.highlightNode.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.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -59,10 +59,11 @@ public class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
|||
let actionText: String?
|
||||
let action: (() -> Void)?
|
||||
public let sectionId: ItemListSectionId
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public let isAlwaysPlain: Bool = true
|
||||
|
||||
public init(presentationData: ItemListPresentationData, text: String, badge: String? = nil, badgeStyle: BadgeStyle? = nil, multiline: Bool = false, activityIndicator: ItemListSectionHeaderActivityIndicator = .none, accessoryText: ItemListSectionHeaderAccessoryText? = nil, actionText: String? = nil, action: (() -> Void)? = nil, sectionId: ItemListSectionId) {
|
||||
public init(presentationData: ItemListPresentationData, text: String, badge: String? = nil, badgeStyle: BadgeStyle? = nil, multiline: Bool = false, activityIndicator: ItemListSectionHeaderActivityIndicator = .none, accessoryText: ItemListSectionHeaderAccessoryText? = nil, actionText: String? = nil, action: (() -> Void)? = nil, sectionId: ItemListSectionId, tag: ItemListItemTag? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.text = text
|
||||
self.badge = badge
|
||||
|
|
@ -73,6 +74,7 @@ public class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
|||
self.actionText = actionText
|
||||
self.action = action
|
||||
self.sectionId = sectionId
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
|
|
@ -125,6 +127,10 @@ public class ItemListSectionHeaderItemNode: ListViewItemNode {
|
|||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ extension IconSwitchNode: ItemListSwitchNodeImpl {
|
|||
|
||||
public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
|
@ -175,6 +176,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.isUserInteractionEnabled = false
|
||||
|
|
@ -234,6 +238,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.switchGestureNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ItemListSwitchItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
|
|
@ -400,14 +418,13 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
|
||||
strongSelf.switchNode.frameColor = item.presentationData.theme.list.itemSwitchColors.frameColor
|
||||
strongSelf.switchNode.contentColor = item.presentationData.theme.list.itemSwitchColors.contentColor
|
||||
strongSelf.switchNode.handleColor = item.presentationData.theme.list.itemSwitchColors.handleColor
|
||||
strongSelf.switchNode.positiveContentColor = item.presentationData.theme.list.itemSwitchColors.positiveColor
|
||||
strongSelf.switchNode.negativeContentColor = item.presentationData.theme.list.itemSwitchColors.negativeColor
|
||||
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.highlightNode.backgroundColor = item.presentationData.theme.list.itemSearchHighlightColor
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
|
|
@ -465,6 +482,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
transition.updateFrame(node: 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))))
|
||||
transition.updateFrame(node: strongSelf.highlightNode, 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))))
|
||||
transition.updateFrame(node: strongSelf.maskNode, frame: strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0))
|
||||
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)))
|
||||
|
|
|
|||
|
|
@ -121,7 +121,12 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN
|
|||
#pragma mark Initialization and teardown
|
||||
|
||||
- (GPUImageRotationMode)rotationForTrack:(AVAsset *)asset {
|
||||
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
|
||||
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
|
||||
if (tracks.count == 0) {
|
||||
return kGPUImageNoRotation;
|
||||
}
|
||||
|
||||
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
|
||||
CGAffineTransform t = [videoTrack preferredTransform];
|
||||
|
||||
if (t.a == -1 && t.d == -1) {
|
||||
|
|
|
|||
|
|
@ -428,9 +428,9 @@
|
|||
if (itemChanged) {
|
||||
[self _playerCleanup];
|
||||
|
||||
if (!item.asFile) {
|
||||
[_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startStrictWithNext:nil file:__FILE_NAME__ line:__LINE__]];
|
||||
}
|
||||
// if (!item.asFile) {
|
||||
// [_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startStrictWithNext:nil file:__FILE_NAME__ line:__LINE__]];
|
||||
// }
|
||||
}
|
||||
|
||||
_scrubberView.allowsTrimming = false;
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/CounterControllerTitleView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import SwiftSignalKit
|
|||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import SearchUI
|
||||
import CounterControllerTitleView
|
||||
|
||||
public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMembersSearchController {
|
||||
private let queue = Queue()
|
||||
|
|
@ -49,7 +50,18 @@ public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMe
|
|||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
self.title = self.presentationData.strings.Channel_Members_Title
|
||||
let title: String
|
||||
switch params.mode {
|
||||
case .ownershipTransfer:
|
||||
//TODO:localize
|
||||
title = "Appoint Another Owner"
|
||||
let titleView = CounterControllerTitleView(theme: self.presentationData.theme)
|
||||
titleView.title = CounterControllerTitle(title: title, counter: " ")
|
||||
self.navigationItem.titleView = titleView
|
||||
default:
|
||||
title = self.presentationData.strings.Channel_Members_Title
|
||||
self.title = title
|
||||
}
|
||||
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
|
||||
|
|
@ -74,16 +86,28 @@ public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMe
|
|||
}
|
||||
strongSelf.presentationData = presentationData
|
||||
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||
|
||||
if let titleView = strongSelf.navigationItem.titleView as? CounterControllerTitleView {
|
||||
titleView.theme = presentationData.theme
|
||||
}
|
||||
})
|
||||
|
||||
let _ = (params.context.account.postbox.loadedPeerWithId(peerId)
|
||||
let _ = (params.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId))
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
strongSelf.title = strongSelf.presentationData.strings.Channel_Subscribers_Title
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
switch self.mode {
|
||||
case .ownershipTransfer:
|
||||
if let titleView = self.navigationItem.titleView as? CounterControllerTitleView {
|
||||
titleView.title = CounterControllerTitle(title: titleView.title.title, counter: peer.compactDisplayTitle)
|
||||
}
|
||||
default:
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
self.title = self.presentationData.strings.Channel_Subscribers_Title
|
||||
} else {
|
||||
strongSelf.title = strongSelf.presentationData.strings.Channel_Members_Title
|
||||
self.title = self.presentationData.strings.Channel_Members_Title
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -390,6 +390,13 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
case .ownershipTransfer:
|
||||
if peer.id == context.account.peerId {
|
||||
continue
|
||||
}
|
||||
if let user = peer as? TelegramUser, user.botInfo != nil || user.flags.contains(.isSupport) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
let renderedParticipant: RenderedChannelParticipant
|
||||
switch participant {
|
||||
|
|
@ -531,7 +538,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||
var label: String?
|
||||
var enabled = true
|
||||
switch mode {
|
||||
case .ban, .promote:
|
||||
case .ban, .promote, .ownershipTransfer:
|
||||
if participant.peer.id == context.account.peerId {
|
||||
continue participantsLoop
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,10 +158,10 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
|
|
@ -172,7 +172,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||
component.cancel(true)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
|
|
@ -496,11 +496,11 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
|||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 36.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 38.0))
|
||||
)
|
||||
|
||||
context.add(star
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 + 6.0))
|
||||
)
|
||||
|
||||
var originY: CGFloat = 0.0
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Foundation
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
|
@ -38,6 +39,11 @@ import PremiumStarComponent
|
|||
import PremiumCoinComponent
|
||||
import EdgeEffect
|
||||
|
||||
public enum PremiumIntroEntryTag {
|
||||
case doNotHideAds
|
||||
}
|
||||
private let doNotHideAdsTag = GenericComponentViewTag()
|
||||
|
||||
public enum PremiumSource: Equatable {
|
||||
public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
|
||||
switch lhs {
|
||||
|
|
@ -2677,7 +2683,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
let _ = accountContext.engine.accountData.updateAdMessagesEnabled(enabled: value).startStandalone()
|
||||
state?.updated(transition: .immediate)
|
||||
})),
|
||||
action: nil
|
||||
action: nil,
|
||||
tag: doNotHideAdsTag
|
||||
))))
|
||||
|
||||
let adsInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Business_AdsInfo, attributes: termsMarkdownAttributes, textAlignment: .natural
|
||||
|
|
@ -3918,6 +3925,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
private let screenContext: ScreenContext
|
||||
fileprivate let mode: Mode
|
||||
private let focusOnItemTag: PremiumIntroEntryTag?
|
||||
|
||||
private var didSetReady = false
|
||||
private let _ready = Promise<Bool>()
|
||||
|
|
@ -3932,13 +3940,14 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||
|
||||
private let overNavigationContainer: UIView
|
||||
|
||||
public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) {
|
||||
self.init(screenContext: .accountContext(context), mode: mode, source: source, modal: modal, forceDark: forceDark, forceHasPremium: forceHasPremium)
|
||||
public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false, focusOnItemTag: PremiumIntroEntryTag? = nil) {
|
||||
self.init(screenContext: .accountContext(context), mode: mode, source: source, modal: modal, forceDark: forceDark, forceHasPremium: forceHasPremium, focusOnItemTag: focusOnItemTag)
|
||||
}
|
||||
|
||||
public init(screenContext: ScreenContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) {
|
||||
public init(screenContext: ScreenContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false, focusOnItemTag: PremiumIntroEntryTag? = nil) {
|
||||
self.screenContext = screenContext
|
||||
self.mode = mode
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
let presentationData = screenContext.presentationData
|
||||
|
||||
|
|
@ -4066,6 +4075,20 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
if let focusOnItemTag = self.focusOnItemTag {
|
||||
Queue.mainQueue().after(0.1, {
|
||||
switch focusOnItemTag {
|
||||
case .doNotHideAds:
|
||||
if let view = self.node.hostView.findTaggedView(tag: doNotHideAdsTag) as? ListActionItemComponent.View {
|
||||
if let scrollView = findParentScrollView(view: view) {
|
||||
view.displayHighlight()
|
||||
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.bounds.height), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
|
|
|
|||
|
|
@ -857,10 +857,10 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
|
|
@ -872,7 +872,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
component.cancel()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
|
|
@ -1519,7 +1519,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 36.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 38.0))
|
||||
)
|
||||
|
||||
var textSize: CGSize
|
||||
|
|
|
|||
|
|
@ -163,10 +163,10 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
|
|
@ -177,7 +177,7 @@ private final class SheetContent: CombinedComponent {
|
|||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
|
|
|
|||
|
|
@ -159,25 +159,25 @@ private final class SheetContent: CombinedComponent {
|
|||
textString = ""
|
||||
}
|
||||
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 36.0)
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 38.0)
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
|
|
|
|||
|
|
@ -135,6 +135,10 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AlertComponent",
|
||||
"//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/AvatarEditorScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -40,13 +40,22 @@ func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl
|
|||
for item in items {
|
||||
if case let .text(itemText, _) = item, case let .url(text, url, _) = itemText {
|
||||
let (_, anchor) = extractAnchor(string: url)
|
||||
guard let anchor else {
|
||||
continue
|
||||
}
|
||||
var index = nextIndex
|
||||
if suggestAccountDeletion && (anchor?.contains("delete-my-account") ?? false) {
|
||||
if suggestAccountDeletion && anchor.contains("delete-my-account") {
|
||||
index = 1
|
||||
} else {
|
||||
nextIndex += 1
|
||||
}
|
||||
let item = SettingsSearchableItem(id: .faq(index), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, _, present in
|
||||
let item = SettingsSearchableItem(
|
||||
id: "faq/\(anchor)",
|
||||
title: text.plainText,
|
||||
alternate: [],
|
||||
icon: .faq,
|
||||
breadcrumbs: [strings.SettingsSearch_FAQ, currentSection],
|
||||
present: { context, _, present in
|
||||
let controller = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel))
|
||||
present(.push, controller)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -41,6 +41,23 @@ private enum AutodownloadMediaCategorySection: Int32 {
|
|||
case types
|
||||
}
|
||||
|
||||
public enum AutodownloadMediaCategoryEntryTag: ItemListItemTag, Equatable {
|
||||
case master
|
||||
case usage
|
||||
case photos
|
||||
case stories
|
||||
case videos
|
||||
case files
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? AutodownloadMediaCategoryEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||
case master(PresentationTheme, String, Bool)
|
||||
case dataUsageHeader(PresentationTheme, String)
|
||||
|
|
@ -155,31 +172,31 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
|||
case let .master(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleMaster(value)
|
||||
})
|
||||
}, tag: AutodownloadMediaCategoryEntryTag.master)
|
||||
case let .dataUsageHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .dataUsageItem(theme, strings, value, customPosition, enabled):
|
||||
return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, systemStyle: .glass, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in
|
||||
arguments.changePreset(preset)
|
||||
})
|
||||
}, tag: AutodownloadMediaCategoryEntryTag.usage)
|
||||
case let .typesHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .photos(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.photo)
|
||||
})
|
||||
}, tag: AutodownloadMediaCategoryEntryTag.photos)
|
||||
case let .stories(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Stories")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.story)
|
||||
})
|
||||
}, tag: AutodownloadMediaCategoryEntryTag.stories)
|
||||
case let .videos(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.video)
|
||||
})
|
||||
}, tag: AutodownloadMediaCategoryEntryTag.videos)
|
||||
case let .files(_, text, value, enabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.customize(.file)
|
||||
})
|
||||
}, tag: AutodownloadMediaCategoryEntryTag.files)
|
||||
case let .voiceMessagesInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
|
|
@ -300,7 +317,7 @@ private func autodownloadMediaConnectionTypeControllerEntries(presentationData:
|
|||
return entries
|
||||
}
|
||||
|
||||
func autodownloadMediaConnectionTypeController(context: AccountContext, connectionType: AutomaticDownloadConnectionType) -> ViewController {
|
||||
func autodownloadMediaConnectionTypeController(context: AccountContext, connectionType: AutomaticDownloadConnectionType, focusOnItemTag: AutodownloadMediaCategoryEntryTag? = nil) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let arguments = AutodownloadMediaConnectionTypeControllerArguments(toggleMaster: { value in
|
||||
|
|
@ -368,7 +385,7 @@ func autodownloadMediaConnectionTypeController(context: AccountContext, connecti
|
|||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: autodownloadMediaConnectionTypeControllerEntries(presentationData: presentationData, connectionType: connectionType, settings: automaticMediaDownloadSettings), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: autodownloadMediaConnectionTypeControllerEntries(presentationData: presentationData, connectionType: connectionType, settings: automaticMediaDownloadSettings), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -379,5 +396,20 @@ func autodownloadMediaConnectionTypeController(context: AccountContext, connecti
|
|||
(controller.navigationController as? NavigationController)?.pushViewController(c)
|
||||
}
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
|||
let enabled: Bool
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (AutomaticDownloadDataUsage) -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void, tag: ItemListItemTag? = nil) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.systemStyle = systemStyle
|
||||
|
|
@ -50,6 +51,7 @@ final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
|||
self.enabled = enabled
|
||||
self.sectionId = sectionId
|
||||
self.updated = updated
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -86,7 +88,7 @@ final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
|||
}
|
||||
}
|
||||
|
||||
private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
|
||||
private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
|
@ -103,6 +105,10 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
|
|||
private var item: AutodownloadDataUsagePickerItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
|||
let range: Range<Int64>?
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (Int64) -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, decimalSeparator: String, text: String, value: Int64, range: Range<Int64>?, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, decimalSeparator: String, text: String, value: Int64, range: Range<Int64>?, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void, tag: ItemListItemTag? = nil) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.systemStyle = systemStyle
|
||||
|
|
@ -69,6 +70,7 @@ final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
|||
self.range = range
|
||||
self.sectionId = sectionId
|
||||
self.updated = updated
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -105,7 +107,7 @@ final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
|||
}
|
||||
}
|
||||
|
||||
private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
||||
private final class AutodownloadSizeLimitItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
|
@ -119,6 +121,10 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
|||
private var item: AutodownloadSizeLimitItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ public enum DataAndStorageEntryTag: ItemListItemTag, Equatable {
|
|||
case raiseToListen
|
||||
case autoSave(AutomaticSaveIncomingPeerType)
|
||||
case sensitiveContent
|
||||
case useLessVoiceData
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? DataAndStorageEntryTag, self == other {
|
||||
|
|
@ -409,7 +410,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
|||
case let .useLessVoiceData(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleVoiceUseLessData(value)
|
||||
}, tag: nil)
|
||||
}, tag: DataAndStorageEntryTag.useLessVoiceData)
|
||||
case let .useLessVoiceDataInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .otherHeader(_, text):
|
||||
|
|
@ -1012,6 +1013,20 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
|||
update()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import PresentationDataUtils
|
|||
import AccountContext
|
||||
import UndoUI
|
||||
|
||||
enum ItemType: CaseIterable {
|
||||
public enum EnergySavingItemType: CaseIterable {
|
||||
case autoplayVideo
|
||||
case autoplayGif
|
||||
case loopStickers
|
||||
|
|
@ -88,10 +88,10 @@ enum ItemType: CaseIterable {
|
|||
|
||||
private final class EnergeSavingSettingsScreenArguments {
|
||||
let updateThreshold: (Int32) -> Void
|
||||
let toggleItem: (ItemType) -> Void
|
||||
let toggleItem: (EnergySavingItemType) -> Void
|
||||
let displayDisabledTooltip: () -> Void
|
||||
|
||||
init(updateThreshold: @escaping (Int32) -> Void, toggleItem: @escaping (ItemType) -> Void, displayDisabledTooltip: @escaping () -> Void) {
|
||||
init(updateThreshold: @escaping (Int32) -> Void, toggleItem: @escaping (EnergySavingItemType) -> Void, displayDisabledTooltip: @escaping () -> Void) {
|
||||
self.updateThreshold = updateThreshold
|
||||
self.toggleItem = toggleItem
|
||||
self.displayDisabledTooltip = displayDisabledTooltip
|
||||
|
|
@ -103,19 +103,31 @@ private enum EnergeSavingSettingsScreenSection: Int32 {
|
|||
case items
|
||||
}
|
||||
|
||||
public enum EnergySavingEntryTag: ItemListItemTag, Equatable {
|
||||
case item(EnergySavingItemType)
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? EnergySavingEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry {
|
||||
enum StableId: Hashable {
|
||||
case allHeader
|
||||
case all
|
||||
case allFooter
|
||||
case itemsHeader
|
||||
case item(ItemType)
|
||||
case item(EnergySavingItemType)
|
||||
}
|
||||
|
||||
case allHeader(Bool?)
|
||||
case all(Int32)
|
||||
case allFooter(String)
|
||||
case item(index: Int, type: ItemType, value: Bool, enabled: Bool)
|
||||
case item(index: Int, type: EnergySavingItemType, value: Bool, enabled: Bool)
|
||||
case itemsHeader
|
||||
|
||||
var section: ItemListSectionId {
|
||||
|
|
@ -197,7 +209,7 @@ private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry {
|
|||
arguments.toggleItem(type)
|
||||
}, activatedWhileDisabled: {
|
||||
arguments.displayDisabledTooltip()
|
||||
})
|
||||
}, tag: EnergySavingEntryTag.item(type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -241,14 +253,14 @@ private func energeSavingSettingsScreenEntries(
|
|||
}
|
||||
|
||||
entries.append(.itemsHeader)
|
||||
for type in ItemType.allCases {
|
||||
for type in EnergySavingItemType.allCases {
|
||||
entries.append(.item(index: entries.count, type: type, value: settings.energyUsageSettings[keyPath: type.settingsKeyPath] && itemsEnabled, enabled: itemsEnabled))
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public func energySavingSettingsScreen(context: AccountContext) -> ViewController {
|
||||
public func energySavingSettingsScreen(context: AccountContext, focusOnItemTag: EnergySavingEntryTag? = nil) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
let _ = pushControllerImpl
|
||||
|
||||
|
|
@ -300,7 +312,7 @@ public func energySavingSettingsScreen(context: AccountContext) -> ViewControlle
|
|||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||
animateChanges: false
|
||||
)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: energeSavingSettingsScreenEntries(presentationData: presentationData, settings: automaticMediaDownloadSettings), style: .blocks, emptyStateItem: nil, animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: energeSavingSettingsScreenEntries(presentationData: presentationData, settings: automaticMediaDownloadSettings), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -317,5 +329,20 @@ public func energySavingSettingsScreen(context: AccountContext) -> ViewControlle
|
|||
controller.present(UndoOverlayController(presentationData: presentationData, content: c, elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,20 @@ import AccountContext
|
|||
import TelegramIntents
|
||||
import AccountUtils
|
||||
|
||||
public enum IntentsEntryTag: ItemListItemTag, Equatable {
|
||||
case suggested
|
||||
case suggestBy
|
||||
case reset
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? IntentsEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class IntentsSettingsControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateSettings: (@escaping (IntentsSettings) -> IntentsSettings) -> Void
|
||||
|
|
@ -197,7 +211,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
|||
case let .contacts(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateSettings { $0.withUpdatedContacts(value) }
|
||||
})
|
||||
}, tag: IntentsEntryTag.suggested)
|
||||
case let .savedMessages(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateSettings { $0.withUpdatedSavedMessages(value) }
|
||||
|
|
@ -217,7 +231,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
|||
case let .suggestAll(_, text, value):
|
||||
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
arguments.updateSettings { $0.withUpdatedOnlyShared(false) }
|
||||
})
|
||||
}, tag: IntentsEntryTag.suggestBy)
|
||||
case let .suggestOnlyShared(_, text, value):
|
||||
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
arguments.updateSettings { $0.withUpdatedOnlyShared(true) }
|
||||
|
|
@ -226,7 +240,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
|||
case let .resetAll(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.resetAll()
|
||||
})
|
||||
}, tag: IntentsEntryTag.reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -261,7 +275,7 @@ private func intentsSettingsControllerEntries(context: AccountContext, presentat
|
|||
return entries
|
||||
}
|
||||
|
||||
public func intentsSettingsController(context: AccountContext) -> ViewController {
|
||||
public func intentsSettingsController(context: AccountContext, focusOnItemTag: IntentsEntryTag? = nil) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let arguments = IntentsSettingsControllerArguments(context: context, updateSettings: { f in
|
||||
|
|
@ -317,5 +331,20 @@ public func intentsSettingsController(context: AccountContext) -> ViewController
|
|||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,21 @@ private enum ProxySettingsControllerEntryId: Equatable, Hashable {
|
|||
case server(String, Int32, ProxyServerConnection)
|
||||
}
|
||||
|
||||
public enum ProxySettingsEntryTag: ItemListItemTag, Equatable {
|
||||
case edit
|
||||
case useProxy
|
||||
case shareList
|
||||
case useForCalls
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? ProxySettingsEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
||||
case enabled(PresentationTheme, String, Bool, Bool)
|
||||
case serversHeader(PresentationTheme, String)
|
||||
|
|
@ -207,7 +222,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
arguments.toggleEnabled(value)
|
||||
}
|
||||
})
|
||||
}, tag: ProxySettingsEntryTag.useProxy)
|
||||
case let .serversHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .addServer(_, text, _):
|
||||
|
|
@ -227,11 +242,11 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
|||
case let .shareProxyList(_, text):
|
||||
return ProxySettingsActionItem(presentationData: presentationData, systemStyle: .glass, title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.shareProxyList()
|
||||
})
|
||||
}, tag: ProxySettingsEntryTag.shareList)
|
||||
case let .useForCalls(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleUseForCalls(value)
|
||||
})
|
||||
}, tag: ProxySettingsEntryTag.useForCalls)
|
||||
case let .useForCallsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
|
|
@ -308,12 +323,12 @@ public enum ProxySettingsControllerMode {
|
|||
case modal
|
||||
}
|
||||
|
||||
public func proxySettingsController(context: AccountContext, mode: ProxySettingsControllerMode = .default) -> ViewController {
|
||||
public func proxySettingsController(context: AccountContext, mode: ProxySettingsControllerMode = .default, focusOnItemTag: ProxySettingsEntryTag? = nil) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
return proxySettingsController(accountManager: context.sharedContext.accountManager, sharedContext: context.sharedContext, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData)
|
||||
return proxySettingsController(accountManager: context.sharedContext.accountManager, sharedContext: context.sharedContext, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, focusOnItemTag: focusOnItemTag)
|
||||
}
|
||||
|
||||
public func proxySettingsController(accountManager: AccountManager<TelegramAccountManagerTypes>, sharedContext: SharedAccountContext, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>) -> ViewController {
|
||||
public func proxySettingsController(accountManager: AccountManager<TelegramAccountManagerTypes>, sharedContext: SharedAccountContext, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>, focusOnItemTag: ProxySettingsEntryTag? = nil) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
let stateValue = Atomic(value: ProxySettingsControllerState())
|
||||
|
|
@ -332,6 +347,14 @@ public func proxySettingsController(accountManager: AccountManager<TelegramAccou
|
|||
}
|
||||
}
|
||||
|
||||
if focusOnItemTag == ProxySettingsEntryTag.edit {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.editing = true
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
var shareProxyListImpl: (() -> Void)?
|
||||
|
||||
let arguments = ProxySettingsControllerArguments(toggleEnabled: { value in
|
||||
|
|
@ -431,7 +454,7 @@ public func proxySettingsController(accountManager: AccountManager<TelegramAccou
|
|||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.SocksProxySetup_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: proxySettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, state: state, proxySettings: proxySettings, statuses: statuses, connectionStatus: connectionStatus), style: .blocks)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: proxySettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, state: state, proxySettings: proxySettings, statuses: statuses, connectionStatus: connectionStatus), style: .blocks, ensureVisibleItemTag: focusOnItemTag)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -530,5 +553,19 @@ public func proxySettingsController(accountManager: AccountManager<TelegramAccou
|
|||
})
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ final class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
|||
let editing: Bool
|
||||
let sectionId: ItemListSectionId
|
||||
let action: () -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, icon: ProxySettingsActionIcon = .none, sectionId: ItemListSectionId, editing: Bool, action: @escaping () -> Void) {
|
||||
init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, icon: ProxySettingsActionIcon = .none, sectionId: ItemListSectionId, editing: Bool, action: @escaping () -> Void, tag: ItemListItemTag? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.systemStyle = systemStyle
|
||||
self.title = title
|
||||
|
|
@ -29,6 +30,7 @@ final class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
|||
self.editing = editing
|
||||
self.sectionId = sectionId
|
||||
self.action = action
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -77,7 +79,7 @@ final class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
|||
}
|
||||
}
|
||||
|
||||
private final class ProxySettingsActionItemNode: ListViewItemNode {
|
||||
private final class ProxySettingsActionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
|
@ -89,6 +91,10 @@ private final class ProxySettingsActionItemNode: ListViewItemNode {
|
|||
|
||||
private var item: ProxySettingsActionItem?
|
||||
|
||||
public var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
|
|||
|
|
@ -48,6 +48,20 @@ enum SaveIncomingMediaSection: ItemListSectionId {
|
|||
case deleteAllExceptions
|
||||
}
|
||||
|
||||
public enum SaveIncomingMediaEntryTag: ItemListItemTag, Equatable {
|
||||
case maxVideoSize
|
||||
case addException
|
||||
case deleteExceptions
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? SaveIncomingMediaEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
||||
enum StableId: Hashable {
|
||||
case peer
|
||||
|
|
@ -200,7 +214,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
|||
case let .videoSize(decimalSeparator, text, size):
|
||||
return AutodownloadSizeLimitItem(theme: presentationData.theme, strings: presentationData.strings, systemStyle: .glass, decimalSeparator: decimalSeparator, text: text, value: size, range: nil/*2 * 1024 * 1024 ..< (4 * 1024 * 1024 * 1024)*/, sectionId: self.section, updated: { value in
|
||||
arguments.updateMaximumVideoSize(value)
|
||||
})
|
||||
}, tag: SaveIncomingMediaEntryTag.maxVideoSize)
|
||||
case let .videoInfo(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .exceptionsHeader(title):
|
||||
|
|
@ -209,7 +223,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
|||
let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme)
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: icon, title: title, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: {
|
||||
arguments.openAddException()
|
||||
})
|
||||
}, tag: SaveIncomingMediaEntryTag.addException)
|
||||
case let .exceptionItem(_, peer, label):
|
||||
return ItemListPeerItem(
|
||||
presentationData: presentationData,
|
||||
|
|
@ -248,7 +262,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
|||
case let .deleteAllExceptions(title):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.deleteAllExceptions()
|
||||
})
|
||||
}, tag: SaveIncomingMediaEntryTag.deleteExceptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -352,7 +366,7 @@ private func saveIncomingMediaControllerEntries(presentationData: PresentationDa
|
|||
return entries
|
||||
}
|
||||
|
||||
enum SaveIncomingMediaScope {
|
||||
public enum SaveIncomingMediaScope {
|
||||
case peer(EnginePeer.Id)
|
||||
case addPeer(id: EnginePeer.Id, completion: (MediaAutoSaveConfiguration) -> Void)
|
||||
case peerType(AutomaticSaveIncomingPeerType)
|
||||
|
|
@ -363,7 +377,7 @@ private struct SaveIncomingMediaControllerState: Equatable {
|
|||
var peerIdWithOptions: EnginePeer.Id?
|
||||
}
|
||||
|
||||
func saveIncomingMediaController(context: AccountContext, scope: SaveIncomingMediaScope) -> ViewController {
|
||||
public func saveIncomingMediaController(context: AccountContext, scope: SaveIncomingMediaScope, focusOnItemTag: SaveIncomingMediaEntryTag? = nil) -> ViewController {
|
||||
let stateValue = Atomic(value: SaveIncomingMediaControllerState())
|
||||
let statePromise = ValuePromise<SaveIncomingMediaControllerState>(stateValue.with { $0 })
|
||||
let updateState: ((SaveIncomingMediaControllerState) -> SaveIncomingMediaControllerState) -> Void = { f in
|
||||
|
|
@ -719,7 +733,7 @@ func saveIncomingMediaController(context: AccountContext, scope: SaveIncomingMed
|
|||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: nil, animateChanges: animateChanges)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -743,6 +757,20 @@ func saveIncomingMediaController(context: AccountContext, scope: SaveIncomingMed
|
|||
controller?.dismiss()
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,24 @@ import TelegramCore
|
|||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import SearchUI
|
||||
import ItemListUI
|
||||
|
||||
public enum LocalizationListEntryTag: ItemListItemTag, Equatable {
|
||||
case showButton
|
||||
case translateChats
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? LocalizationListEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalizationListController: ViewController {
|
||||
private let context: AccountContext
|
||||
private let focusOnItemTag: LocalizationListEntryTag?
|
||||
|
||||
private var controllerNode: LocalizationListControllerNode {
|
||||
return self.displayNode as! LocalizationListControllerNode
|
||||
|
|
@ -31,8 +46,9 @@ public class LocalizationListController: ViewController {
|
|||
|
||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||
|
||||
public init(context: AccountContext) {
|
||||
public init(context: AccountContext, focusOnItemTag: LocalizationListEntryTag? = nil) {
|
||||
self.context = context
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
|
|
@ -125,7 +141,7 @@ public class LocalizationListController: ViewController {
|
|||
self?.present(c, in: .window(.root), with: a)
|
||||
}, push: { [weak self] c in
|
||||
self?.push(c)
|
||||
})
|
||||
}, focusOnItemTag: self.focusOnItemTag)
|
||||
|
||||
self.controllerNode.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||
if let strongSelf = self {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ private enum LanguageListEntry: Comparable, Identifiable {
|
|||
case let .translate(text, value):
|
||||
return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, value: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in
|
||||
toggleShowTranslate(value)
|
||||
})
|
||||
}, tag: LocalizationListEntryTag.showButton)
|
||||
case let .translateEntire(text, value, locked):
|
||||
return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !locked, displayLocked: locked, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in
|
||||
if !locked {
|
||||
|
|
@ -103,7 +103,7 @@ private enum LanguageListEntry: Comparable, Identifiable {
|
|||
}
|
||||
}, activatedWhileDisabled: {
|
||||
showPremiumInfo()
|
||||
})
|
||||
}, tag: LocalizationListEntryTag.translateChats)
|
||||
case let .doNotTranslate(text, value):
|
||||
return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, label: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, action: {
|
||||
openDoNotTranslate()
|
||||
|
|
@ -342,6 +342,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
|||
private let requestDeactivateSearch: () -> Void
|
||||
private let present: (ViewController, Any?) -> Void
|
||||
private let push: (ViewController) -> Void
|
||||
private var focusOnItemTag: LocalizationListEntryTag?
|
||||
|
||||
private var didSetReady = false
|
||||
let _ready = ValuePromise<Bool>()
|
||||
|
|
@ -367,7 +368,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
|||
}
|
||||
}
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, navigationBar: NavigationBar, requestActivateSearch: @escaping () -> Void, requestDeactivateSearch: @escaping () -> Void, updateCanStartEditing: @escaping (Bool?) -> Void, present: @escaping (ViewController, Any?) -> Void, push: @escaping (ViewController) -> Void) {
|
||||
init(context: AccountContext, presentationData: PresentationData, navigationBar: NavigationBar, requestActivateSearch: @escaping () -> Void, requestDeactivateSearch: @escaping () -> Void, updateCanStartEditing: @escaping (Bool?) -> Void, present: @escaping (ViewController, Any?) -> Void, push: @escaping (ViewController) -> Void, focusOnItemTag: LocalizationListEntryTag?) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataValue.set(.single(presentationData))
|
||||
|
|
@ -376,6 +377,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
|||
self.requestDeactivateSearch = requestDeactivateSearch
|
||||
self.present = present
|
||||
self.push = push
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: presentationData.theme.list.blocksBackgroundColor, direction: true)
|
||||
|
|
@ -741,6 +743,16 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
|||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf._ready.set(true)
|
||||
|
||||
if let focusOnItemTag = strongSelf.focusOnItemTag {
|
||||
strongSelf.focusOnItemTag = nil
|
||||
|
||||
strongSelf.listNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -897,5 +897,20 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
|
|||
pushControllerImpl = { [weak controller] c in
|
||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,9 +77,12 @@ private enum NotificationsPeerCategorySection: Int32 {
|
|||
}
|
||||
|
||||
public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
||||
case edit
|
||||
case enable
|
||||
case previews
|
||||
case sound
|
||||
case important
|
||||
case deleteExceptions
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? NotificationsPeerCategoryEntryTag, self == other {
|
||||
|
|
@ -284,7 +287,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||
case let .enableImportant(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateEnabledImportant(updatedValue)
|
||||
}, tag: self.tag)
|
||||
}, tag: NotificationsPeerCategoryEntryTag.important)
|
||||
case let .importantInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .optionsHeader(_, text):
|
||||
|
|
@ -314,7 +317,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||
case let .removeAllExceptions(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||
arguments.removeAllExceptions()
|
||||
})
|
||||
}, tag: NotificationsPeerCategoryEntryTag.deleteExceptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -617,6 +620,12 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
|||
updatedMode(result.mode)
|
||||
}
|
||||
|
||||
if focusOnItemTag == .edit {
|
||||
updateState {
|
||||
$0.withUpdatedEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
|
||||
return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: sound) |> deliverOnMainQueue
|
||||
}
|
||||
|
|
@ -1096,5 +1105,20 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
|||
pushControllerImpl = { [weak controller] c in
|
||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,13 +203,19 @@ private func blockedPeersControllerEntries(presentationData: PresentationData, s
|
|||
return entries
|
||||
}
|
||||
|
||||
public func blockedPeersController(context: AccountContext, blockedPeersContext: BlockedPeersContext) -> ViewController {
|
||||
public func blockedPeersController(context: AccountContext, blockedPeersContext: BlockedPeersContext, forceEdit: Bool = false) -> ViewController {
|
||||
let statePromise = ValuePromise(BlockedPeersControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: BlockedPeersControllerState())
|
||||
let updateState: ((BlockedPeersControllerState) -> BlockedPeersControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
if forceEdit {
|
||||
updateState {
|
||||
$0.withUpdatedEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ private final class DataPrivacyControllerArguments {
|
|||
}
|
||||
}
|
||||
|
||||
private enum PrivacyAndSecuritySection: Int32 {
|
||||
private enum DataPrivacySection: Int32 {
|
||||
case contacts
|
||||
case frequentContacts
|
||||
case chats
|
||||
|
|
@ -45,7 +45,24 @@ private enum PrivacyAndSecuritySection: Int32 {
|
|||
case bots
|
||||
}
|
||||
|
||||
private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
public enum DataPrivacyEntryTag: ItemListItemTag, Equatable {
|
||||
case deleteSynced
|
||||
case syncContacts
|
||||
case suggestContacts
|
||||
case deleteCloudDrafts
|
||||
case clearPaymentInfo
|
||||
case linkPreviews
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? DataPrivacyEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum DataPrivacyEntry: ItemListNodeEntry {
|
||||
case contactsHeader(PresentationTheme, String)
|
||||
case deleteContacts(PresentationTheme, String, Bool)
|
||||
case syncContacts(PresentationTheme, String, Bool)
|
||||
|
|
@ -70,17 +87,17 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
|||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .contactsHeader, .deleteContacts, .syncContacts, .syncContactsInfo:
|
||||
return PrivacyAndSecuritySection.contacts.rawValue
|
||||
return DataPrivacySection.contacts.rawValue
|
||||
case .frequentContacts, .frequentContactsInfo:
|
||||
return PrivacyAndSecuritySection.frequentContacts.rawValue
|
||||
return DataPrivacySection.frequentContacts.rawValue
|
||||
case .chatsHeader, .deleteCloudDrafts:
|
||||
return PrivacyAndSecuritySection.chats.rawValue
|
||||
return DataPrivacySection.chats.rawValue
|
||||
case .paymentHeader, .clearPaymentInfo, .paymentInfo:
|
||||
return PrivacyAndSecuritySection.payments.rawValue
|
||||
return DataPrivacySection.payments.rawValue
|
||||
case .secretChatLinkPreviewsHeader, .secretChatLinkPreviews, .secretChatLinkPreviewsInfo:
|
||||
return PrivacyAndSecuritySection.secretChats.rawValue
|
||||
return DataPrivacySection.secretChats.rawValue
|
||||
case .botList:
|
||||
return PrivacyAndSecuritySection.bots.rawValue
|
||||
return DataPrivacySection.bots.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +141,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
|||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PrivacyAndSecurityEntry, rhs: PrivacyAndSecurityEntry) -> Bool {
|
||||
static func ==(lhs: DataPrivacyEntry, rhs: DataPrivacyEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .contactsHeader(lhsTheme, lhsText):
|
||||
if case let .contactsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
|
|
@ -219,7 +236,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
|||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PrivacyAndSecurityEntry, rhs: PrivacyAndSecurityEntry) -> Bool {
|
||||
static func <(lhs: DataPrivacyEntry, rhs: DataPrivacyEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
|
|
@ -231,17 +248,17 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
|||
case let .deleteContacts(_, text, value):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.deleteContacts()
|
||||
})
|
||||
}, tag: DataPrivacyEntryTag.deleteSynced)
|
||||
case let .syncContacts(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateSyncContacts(updatedValue)
|
||||
})
|
||||
}, tag: DataPrivacyEntryTag.syncContacts)
|
||||
case let .syncContactsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .frequentContacts(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateSuggestFrequentContacts(updatedValue)
|
||||
})
|
||||
}, tag: DataPrivacyEntryTag.suggestContacts)
|
||||
case let .frequentContactsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .chatsHeader(_, text):
|
||||
|
|
@ -249,13 +266,13 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
|||
case let .deleteCloudDrafts(_, text, value):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.deleteCloudDrafts()
|
||||
})
|
||||
}, tag: DataPrivacyEntryTag.deleteCloudDrafts)
|
||||
case let .paymentHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .clearPaymentInfo(_, text, enabled):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.clearPaymentInfo()
|
||||
})
|
||||
}, tag: DataPrivacyEntryTag.clearPaymentInfo)
|
||||
case let .paymentInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .secretChatLinkPreviewsHeader(_, text):
|
||||
|
|
@ -263,7 +280,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
|||
case let .secretChatLinkPreviews(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateSecretChatLinkPreviews(updatedValue)
|
||||
})
|
||||
}, tag: DataPrivacyEntryTag.linkPreviews)
|
||||
case let .secretChatLinkPreviewsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case .botList:
|
||||
|
|
@ -289,8 +306,8 @@ private struct DataPrivacyControllerState: Equatable {
|
|||
var deletingCloudDrafts: Bool = false
|
||||
}
|
||||
|
||||
private func dataPrivacyControllerEntries(presentationData: PresentationData, state: DataPrivacyControllerState, secretChatLinkPreviews: Bool?, synchronizeDeviceContacts: Bool, frequentContacts: Bool, hasBotSettings: Bool) -> [PrivacyAndSecurityEntry] {
|
||||
var entries: [PrivacyAndSecurityEntry] = []
|
||||
private func dataPrivacyControllerEntries(presentationData: PresentationData, state: DataPrivacyControllerState, secretChatLinkPreviews: Bool?, synchronizeDeviceContacts: Bool, frequentContacts: Bool, hasBotSettings: Bool) -> [DataPrivacyEntry] {
|
||||
var entries: [DataPrivacyEntry] = []
|
||||
|
||||
entries.append(.contactsHeader(presentationData.theme, presentationData.strings.Privacy_ContactsTitle))
|
||||
entries.append(.deleteContacts(presentationData.theme, presentationData.strings.Privacy_ContactsReset, !state.deletingContacts))
|
||||
|
|
@ -317,7 +334,7 @@ private func dataPrivacyControllerEntries(presentationData: PresentationData, st
|
|||
return entries
|
||||
}
|
||||
|
||||
public func dataPrivacyController(context: AccountContext) -> ViewController {
|
||||
public func dataPrivacyController(context: AccountContext, focusOnItemTag: DataPrivacyEntryTag? = nil) -> ViewController {
|
||||
let statePromise = ValuePromise(DataPrivacyControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: DataPrivacyControllerState())
|
||||
let updateState: ((DataPrivacyControllerState) -> DataPrivacyControllerState) -> Void = { f in
|
||||
|
|
@ -434,7 +451,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
|||
}
|
||||
if canBegin {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_ContactsResetConfirmation, actions: [TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_ContactsResetConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.Common_Delete, action: {
|
||||
var begin = false
|
||||
updateState { state in
|
||||
var state = state
|
||||
|
|
@ -461,7 +478,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
|||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Privacy_ContactsReset_ContactsDeleted, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return false }))
|
||||
}))
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})]))
|
||||
})]))
|
||||
}
|
||||
}, updateSyncContacts: { value in
|
||||
let _ = context.engine.contacts.updateIsContactSynchronizationEnabled(isContactSynchronizationEnabled: value).start()
|
||||
|
|
@ -556,7 +573,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
|||
|
||||
let animateChanges = false
|
||||
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataPrivacyControllerEntries(presentationData: presentationData, state: state, secretChatLinkPreviews: secretChatLinkPreviews, synchronizeDeviceContacts: synchronizeDeviceContacts, frequentContacts: suggestRecentPeers, hasBotSettings: hasBotSettings), style: .blocks, animateChanges: animateChanges)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataPrivacyControllerEntries(presentationData: presentationData, state: state, secretChatLinkPreviews: secretChatLinkPreviews, synchronizeDeviceContacts: synchronizeDeviceContacts, frequentContacts: suggestRecentPeers, hasBotSettings: hasBotSettings), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: animateChanges)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -572,5 +589,19 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
|||
controller?.push(c)
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,18 @@ private enum GlobalAutoremoveSection: Int32 {
|
|||
case general
|
||||
}
|
||||
|
||||
public enum GlobalAutoremoveEntryTag: ItemListItemTag, Equatable {
|
||||
case setCustom
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? GlobalAutoremoveEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||
case header
|
||||
case sectionHeader(String)
|
||||
|
|
@ -126,7 +138,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
|||
case let .customAction(text):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openCustomValue()
|
||||
})
|
||||
}, tag: GlobalAutoremoveEntryTag.setCustom)
|
||||
case let .info(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||
arguments.infoLinkAction()
|
||||
|
|
@ -188,7 +200,7 @@ private func globalAutoremoveScreenEntries(presentationData: PresentationData, s
|
|||
return entries
|
||||
}
|
||||
|
||||
public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, updated: @escaping (Int32) -> Void) -> ViewController {
|
||||
public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, updated: @escaping (Int32) -> Void, focusOnItemTag: GlobalAutoremoveEntryTag? = nil) -> ViewController {
|
||||
let initialState = GlobalAutoremoveScreenState(
|
||||
additionalValues: Set([initialValue]),
|
||||
updatedValue: initialValue
|
||||
|
|
@ -430,7 +442,7 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32,
|
|||
let animateChanges = false
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges, scrollEnabled: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges, scrollEnabled: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -461,5 +473,19 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32,
|
|||
controller?.dismiss()
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,19 @@ private enum IncomingMessagePrivacySection: Int32 {
|
|||
case exceptions
|
||||
}
|
||||
|
||||
public enum IncomingMessagePrivacyEntryTag: ItemListItemTag, Equatable {
|
||||
case setPrice
|
||||
case removeFee
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? IncomingMessagePrivacyEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||
case header
|
||||
case optionEverybody(value: GlobalPrivacySettings.NonContactChatsPrivacy)
|
||||
|
|
@ -166,7 +179,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
|||
case let .exceptions(count):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.Privacy_Messages_RemoveFee, label: count > 0 ? "\(count)" : "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openExceptions()
|
||||
})
|
||||
}, tag: IncomingMessagePrivacyEntryTag.removeFee)
|
||||
case .exceptionsInfo:
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_RemoveFeeInfo), sectionId: self.section)
|
||||
}
|
||||
|
|
@ -212,7 +225,7 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD
|
|||
return entries
|
||||
}
|
||||
|
||||
public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController {
|
||||
public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void, focusOnItemTag: IncomingMessagePrivacyEntryTag? = nil) -> ViewController {
|
||||
var disableFor: [EnginePeer.Id: SelectivePrivacyPeer] = [:]
|
||||
if case let .enableContacts(value, _, _, _) = exceptions {
|
||||
disableFor = value
|
||||
|
|
@ -424,7 +437,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
|
|||
let animateChanges = false
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges, scrollEnabled: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges, scrollEnabled: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -468,5 +481,19 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
|
|||
controller?.dismiss()
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,21 @@ private enum PasscodeOptionsSection: Int32 {
|
|||
case options
|
||||
}
|
||||
|
||||
public enum PasscodeOptionsEntryTag: ItemListItemTag, Equatable {
|
||||
case togglePasscode
|
||||
case changePasscode
|
||||
case autolock
|
||||
case touchId
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? PasscodeOptionsEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PasscodeOptionsEntry: ItemListNodeEntry {
|
||||
case togglePasscode(PresentationTheme, String, Bool)
|
||||
case changePasscode(PresentationTheme, String)
|
||||
|
|
@ -114,21 +129,21 @@ private enum PasscodeOptionsEntry: ItemListNodeEntry {
|
|||
if value {
|
||||
arguments.turnPasscodeOff()
|
||||
}
|
||||
})
|
||||
}, tag: PasscodeOptionsEntryTag.togglePasscode)
|
||||
case let .changePasscode(_, title):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.changePasscode()
|
||||
})
|
||||
}, tag: PasscodeOptionsEntryTag.changePasscode)
|
||||
case let .settingInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .autoLock(_, title, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.changePasscodeTimeout()
|
||||
})
|
||||
}, tag: PasscodeOptionsEntryTag.autolock)
|
||||
case let .touchId(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.changeTouchId(value)
|
||||
})
|
||||
}, tag: PasscodeOptionsEntryTag.touchId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +221,7 @@ private func passcodeOptionsControllerEntries(presentationData: PresentationData
|
|||
return entries
|
||||
}
|
||||
|
||||
func passcodeOptionsController(context: AccountContext) -> ViewController {
|
||||
func passcodeOptionsController(context: AccountContext, focusOnItemTag: PasscodeOptionsEntryTag? = nil) -> ViewController {
|
||||
let initialState = PasscodeOptionsControllerState()
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
|
|
@ -360,7 +375,7 @@ func passcodeOptionsController(context: AccountContext) -> ViewController {
|
|||
|> map { presentationData, state, passcodeOptionsData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PasscodeSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: passcodeOptionsControllerEntries(presentationData: presentationData, state: state, passcodeOptionsData: passcodeOptionsData), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: passcodeOptionsControllerEntries(presentationData: presentationData, state: state, passcodeOptionsData: passcodeOptionsData), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
|
|
@ -383,6 +398,20 @@ func passcodeOptionsController(context: AccountContext) -> ViewController {
|
|||
(controller?.navigationController as? NavigationController)?.replaceTopController(c, animated: animated)
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1629,5 +1629,19 @@ public func privacyAndSecurityController(
|
|||
pushControllerImpl?(controller, true)
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,20 @@ private struct SortIndex: Comparable {
|
|||
}
|
||||
}
|
||||
|
||||
public enum RecentSessionsEntryTag: ItemListItemTag, Equatable {
|
||||
case edit
|
||||
case terminateOtherSessions
|
||||
case autoTerminate
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? RecentSessionsEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||
case header(SortIndex, String)
|
||||
case currentSessionHeader(SortIndex, String)
|
||||
|
|
@ -341,7 +355,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
|||
case let .terminateOtherSessions(_, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||
arguments.terminateOtherSessions()
|
||||
})
|
||||
}, tag: RecentSessionsEntryTag.terminateOtherSessions)
|
||||
case let .terminateAllWebSessions(_, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||
arguments.terminateAllWebSessions()
|
||||
|
|
@ -403,7 +417,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
|||
case let .ttlTimeout(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.setupAuthorizationTTL()
|
||||
}, tag: PrivacyAndSecurityEntryTag.accountTimeout)
|
||||
}, tag: RecentSessionsEntryTag.autoTerminate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -560,13 +574,19 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
|
|||
private final class RecentSessionsControllerImpl: ItemListController, RecentSessionsController {
|
||||
}
|
||||
|
||||
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext, websitesOnly: Bool) -> ViewController & RecentSessionsController {
|
||||
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext, websitesOnly: Bool, focusOnItemTag: RecentSessionsEntryTag? = nil) -> ViewController & RecentSessionsController {
|
||||
let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: RecentSessionsControllerState())
|
||||
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
if focusOnItemTag == .edit {
|
||||
updateState {
|
||||
$0.withUpdatedEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
activeSessionsContext.loadMore()
|
||||
webSessionsContext.loadMore()
|
||||
|
||||
|
|
@ -865,7 +885,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: crossfadeState, animateChanges: animateChanges, scrollEnabled: emptyStateItem == nil)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: emptyStateItem, crossfadeState: crossfadeState, animateChanges: animateChanges, scrollEnabled: emptyStateItem == nil)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
|
|
@ -891,5 +911,19 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
controller?.dismiss()
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,30 @@ private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer],
|
|||
}
|
||||
}
|
||||
|
||||
public enum SelectivePrivacyEntryTag: ItemListItemTag, Equatable {
|
||||
case neverAllow
|
||||
case alwaysAllow
|
||||
case lastSeenHideReadTime
|
||||
case birthdaySetup
|
||||
case giftsShowButton
|
||||
case giftsAcceptedTypes
|
||||
case photoSetPublic
|
||||
case photoUpdatePublic
|
||||
case photoRemovePublic
|
||||
case callsP2PNeverAllow
|
||||
case callsP2PAlwaysAllow
|
||||
case callsP2P
|
||||
case callsIntegration
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? SelectivePrivacyEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||
case forwardsPreviewHeader(PresentationTheme, String)
|
||||
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
|
||||
|
|
@ -606,11 +630,11 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
|||
case let .disableFor(_, title, value, isEnabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSelective(.main, false)
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.neverAllow)
|
||||
case let .enableFor(_, title, value, isEnabled):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSelective(.main, true)
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.alwaysAllow)
|
||||
case let .peersInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .callsP2PHeader(_, text):
|
||||
|
|
@ -632,17 +656,17 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
|||
case let .callsP2PDisableFor(_, title, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSelective(.callP2P, false)
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.callsP2PNeverAllow)
|
||||
case let .callsP2PEnableFor(_, title, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSelective(.callP2P, true)
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.callsP2PAlwaysAllow)
|
||||
case let .callsP2PPeersInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .callsIntegrationEnabled(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateCallIntegrationEnabled?(value)
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.callsIntegration)
|
||||
case let .callsIntegrationInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .phoneDiscoveryHeader(_, text):
|
||||
|
|
@ -662,18 +686,18 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
|||
case let .setPublicPhoto(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPhotoIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
||||
arguments.setPublicPhoto?()
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.photoSetPublic)
|
||||
case let .removePublicPhoto(_, text, peer, image, completeImage):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: completeImage, iconSignal: completeImage == nil ? peerAvatarCompleteImage(account: arguments.context.account, peer: peer, forceProvidedRepresentation: true, representation: image?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28)), size: CGSize(width: 28.0, height: 28.0)) : nil, title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||
arguments.removePublicPhoto?()
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.photoRemovePublic)
|
||||
case let .publicPhotoInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||
})
|
||||
case let .hideReadTime(_, text, enabled, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateHideReadTime?(value)
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.lastSeenHideReadTime)
|
||||
case let .hideReadTimeInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .subscribeToPremium(_, text):
|
||||
|
|
@ -683,7 +707,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
|||
case let .subscribeToPremiumInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .disallowedGiftsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section, tag: SelectivePrivacyEntryTag.giftsAcceptedTypes)
|
||||
case let .disallowedGiftsUnlimited(_, text, isLocked, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
if !isLocked {
|
||||
|
|
@ -747,7 +771,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
|||
if available {
|
||||
arguments.displayLockedGiftsInfo()
|
||||
}
|
||||
})
|
||||
}, tag: SelectivePrivacyEntryTag.giftsShowButton)
|
||||
case let .showGiftButtonInfo(_, text):
|
||||
let attributedString = NSMutableAttributedString(string: text, font: Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize), textColor: presentationData.theme.list.freeTextColor)
|
||||
if let range = attributedString.string.range(of: "#") {
|
||||
|
|
@ -1216,6 +1240,7 @@ public func selectivePrivacySettingsController(
|
|||
requestPublicPhotoSetup: ((@escaping (UIImage?) -> Void) -> Void)? = nil,
|
||||
requestPublicPhotoRemove: ((@escaping () -> Void) -> Void)? = nil,
|
||||
openedFromBirthdayScreen: Bool = false,
|
||||
focusOnItemTag: SelectivePrivacyEntryTag? = nil,
|
||||
updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?, GlobalPrivacySettings?) -> Void
|
||||
) -> ViewController {
|
||||
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
|
|
@ -1910,5 +1935,20 @@ public func selectivePrivacySettingsController(
|
|||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,19 +50,15 @@ private enum TwoStepVerificationUnlockSettingsSection: Int32 {
|
|||
case email
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationUnlockSettingsEntryTag: ItemListItemTag {
|
||||
public enum TwoStepVerificationUnlockSettingsEntryTag: ItemListItemTag {
|
||||
case password
|
||||
case change
|
||||
case disable
|
||||
case changeEmail
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? TwoStepVerificationUnlockSettingsEntryTag {
|
||||
switch self {
|
||||
case .password:
|
||||
if case .password = other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? TwoStepVerificationUnlockSettingsEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
|
@ -159,15 +155,15 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
|
|||
case let .changePassword(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSetupPassword()
|
||||
})
|
||||
}, tag: TwoStepVerificationUnlockSettingsEntryTag.change)
|
||||
case let .turnPasswordOff(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openDisablePassword()
|
||||
})
|
||||
}, tag: TwoStepVerificationUnlockSettingsEntryTag.disable)
|
||||
case let .setupRecoveryEmail(_, text):
|
||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSetupEmail()
|
||||
})
|
||||
}, tag: TwoStepVerificationUnlockSettingsEntryTag.changeEmail)
|
||||
case let .passwordInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .pendingEmailConfirmInfo(_, text):
|
||||
|
|
@ -283,7 +279,7 @@ public enum TwoStepVerificationUnlockSettingsControllerData: Equatable {
|
|||
case manage(password: String, emailSet: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
|
||||
}
|
||||
|
||||
public func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: TwoStepVerificationUnlockSettingsControllerMode, openSetupPasswordImmediately: Bool = false) -> ViewController {
|
||||
public func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: TwoStepVerificationUnlockSettingsControllerMode, openSetupPasswordImmediately: Bool = false, focusOnItemTag: TwoStepVerificationUnlockSettingsEntryTag? = nil) -> ViewController {
|
||||
let initialState = TwoStepVerificationUnlockSettingsControllerState()
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
|
|
@ -463,7 +459,7 @@ public func twoStepVerificationUnlockSettingsController(context: AccountContext,
|
|||
return state
|
||||
}
|
||||
|
||||
replaceControllerImpl?(twoStepVerificationUnlockSettingsController(context: context, mode: .manage(password: password, email: settings.email, pendingEmail: pendingEmail, hasSecureValues: settings.secureSecret != nil)), true)
|
||||
replaceControllerImpl?(twoStepVerificationUnlockSettingsController(context: context, mode: .manage(password: password, email: settings.email, pendingEmail: pendingEmail, hasSecureValues: settings.secureSecret != nil), focusOnItemTag: focusOnItemTag), true)
|
||||
}, error: { error in
|
||||
updateState { state in
|
||||
var state = state
|
||||
|
|
@ -969,7 +965,7 @@ public func twoStepVerificationUnlockSettingsController(context: AccountContext,
|
|||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: twoStepVerificationUnlockSettingsControllerEntries(presentationData: presentationData, state: state, data: data), style: .blocks, focusItemTag: didAppear ? TwoStepVerificationUnlockSettingsEntryTag.password : nil, emptyStateItem: emptyStateItem, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: twoStepVerificationUnlockSettingsControllerEntries(presentationData: presentationData, state: state, data: data), style: .blocks, focusItemTag: focusOnItemTag ?? (didAppear ? TwoStepVerificationUnlockSettingsEntryTag.password : nil), emptyStateItem: emptyStateItem, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -1041,5 +1037,19 @@ public func twoStepVerificationUnlockSettingsController(context: AccountContext,
|
|||
}))
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,22 @@ private enum ReactionNotificationSettingsSection: Int32 {
|
|||
case options
|
||||
}
|
||||
|
||||
public enum ReactionNotificationSettingsEntryTag: ItemListItemTag {
|
||||
case messages
|
||||
case stories
|
||||
case showSender
|
||||
case sound
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? ReactionNotificationSettingsEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum ReactionNotificationSettingsEntry: ItemListNodeEntry {
|
||||
enum StableId: Hashable {
|
||||
case categoriesHeader
|
||||
|
|
@ -169,23 +185,23 @@ private enum ReactionNotificationSettingsEntry: ItemListNodeEntry {
|
|||
arguments.toggleMessages(value)
|
||||
}, action: {
|
||||
arguments.openMessages()
|
||||
})
|
||||
}, tag: ReactionNotificationSettingsEntryTag.messages)
|
||||
case let .stories(title, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleStories(value)
|
||||
}, action: {
|
||||
arguments.openStories()
|
||||
})
|
||||
}, tag: ReactionNotificationSettingsEntryTag.stories)
|
||||
case let .optionsHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .previews(text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updatePreviews(value)
|
||||
})
|
||||
}, tag: ReactionNotificationSettingsEntryTag.showSender)
|
||||
case let .sound(text, value, sound):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.openSound(sound)
|
||||
}, tag: self.tag)
|
||||
}, tag: ReactionNotificationSettingsEntryTag.sound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,7 +271,8 @@ private struct ReactionNotificationSettingsState: Equatable {
|
|||
}
|
||||
|
||||
public func reactionNotificationSettingsController(
|
||||
context: AccountContext
|
||||
context: AccountContext,
|
||||
focusOnItemTag: ReactionNotificationSettingsEntryTag? = nil
|
||||
) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
|
@ -399,7 +416,7 @@ public func reactionNotificationSettingsController(
|
|||
let title: String = presentationData.strings.Notifications_Reactions_Title
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
@ -411,5 +428,20 @@ public func reactionNotificationSettingsController(
|
|||
pushControllerImpl = { [weak controller] c in
|
||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ extension SettingsSearchableItemIcon {
|
|||
return PresentationResourcesSettings.support
|
||||
case .faq:
|
||||
return PresentationResourcesSettings.faq
|
||||
case .tips:
|
||||
return PresentationResourcesSettings.tips
|
||||
case .chatFolders:
|
||||
return PresentationResourcesSettings.chatFolders
|
||||
case .deleteAccount:
|
||||
|
|
@ -56,24 +58,42 @@ extension SettingsSearchableItemIcon {
|
|||
return PresentationResourcesSettings.devices
|
||||
case .premium:
|
||||
return PresentationResourcesSettings.premium
|
||||
case .business:
|
||||
return PresentationResourcesSettings.business
|
||||
case .stars:
|
||||
return PresentationResourcesSettings.stars
|
||||
case .ton:
|
||||
return PresentationResourcesSettings.ton
|
||||
case .stories:
|
||||
return PresentationResourcesSettings.stories
|
||||
case .myProfile:
|
||||
return PresentationResourcesSettings.myProfile
|
||||
case .gift:
|
||||
return PresentationResourcesSettings.premiumGift
|
||||
case .powerSaving:
|
||||
return PresentationResourcesSettings.powerSaving
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class SettingsSearchInteraction {
|
||||
let openItem: (SettingsSearchableItem) -> Void
|
||||
let deleteRecentItem: (SettingsSearchableItemId) -> Void
|
||||
let openItemContextMenu: (SettingsSearchableItem, ContextExtractedContentContainingNode, CGRect, UIGestureRecognizer?) -> Void
|
||||
let deleteRecentItem: (AnyHashable) -> Void
|
||||
|
||||
init(openItem: @escaping (SettingsSearchableItem) -> Void, deleteRecentItem: @escaping (SettingsSearchableItemId) -> Void) {
|
||||
init(
|
||||
openItem: @escaping (SettingsSearchableItem) -> Void,
|
||||
openItemContextMenu: @escaping (SettingsSearchableItem, ContextExtractedContentContainingNode, CGRect, UIGestureRecognizer?) -> Void,
|
||||
deleteRecentItem: @escaping (AnyHashable) -> Void
|
||||
) {
|
||||
self.openItem = openItem
|
||||
self.openItemContextMenu = openItemContextMenu
|
||||
self.deleteRecentItem = deleteRecentItem
|
||||
}
|
||||
}
|
||||
|
||||
private enum SettingsSearchEntryStableId: Hashable {
|
||||
case result(SettingsSearchableItemId)
|
||||
case result(AnyHashable)
|
||||
}
|
||||
|
||||
private enum SettingsSearchEntry: Comparable, Identifiable {
|
||||
|
|
@ -132,7 +152,7 @@ private func preparedSettingsSearchContainerTransition(theme: PresentationTheme,
|
|||
}
|
||||
|
||||
private enum SettingsSearchRecentEntryStableId: Hashable {
|
||||
case recent(SettingsSearchableItemId)
|
||||
case recent(AnyHashable)
|
||||
}
|
||||
|
||||
private enum SettingsSearchRecentEntry: Comparable, Identifiable {
|
||||
|
|
@ -192,7 +212,11 @@ private enum SettingsSearchRecentEntry: Comparable, Identifiable {
|
|||
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: SettingsSearchInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .recent(_, item, header):
|
||||
return SettingsSearchRecentItem(account: account, theme: theme, strings: strings, title: item.title, breadcrumbs: item.breadcrumbs, isFaq: false, action: {
|
||||
var title = item.title
|
||||
if title.isEmpty, let id = item.id.base as? String {
|
||||
title = id
|
||||
}
|
||||
return SettingsSearchRecentItem(account: account, theme: theme, strings: strings, title: title, breadcrumbs: item.breadcrumbs, isFaq: false, action: {
|
||||
interaction.openItem(item)
|
||||
}, deleted: {
|
||||
interaction.deleteRecentItem(item.id)
|
||||
|
|
@ -244,7 +268,19 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
private var presentationDataDisposable: Disposable?
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
|
||||
public init(context: AccountContext, openResult: @escaping (SettingsSearchableItem) -> Void, resolvedFaqUrl: Signal<ResolvedUrl?, NoError>, exceptionsList: Signal<NotificationExceptionsList?, NoError>, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal<AccountPrivacySettings?, NoError>, hasTwoStepAuth: Signal<Bool?, NoError>, twoStepAuthData: Signal<TwoStepVerificationAccessConfiguration?, NoError>, activeSessionsContext: Signal<ActiveSessionsContext?, NoError>, webSessionsContext: Signal<WebSessionsContext?, NoError>) {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
openResult: @escaping (SettingsSearchableItem) -> Void,
|
||||
openContextMenu: @escaping (SettingsSearchableItem, ContextExtractedContentContainingNode, CGRect, UIGestureRecognizer?) -> Void,
|
||||
resolvedFaqUrl: Signal<ResolvedUrl?, NoError>,
|
||||
exceptionsList: Signal<NotificationExceptionsList?, NoError>,
|
||||
archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>,
|
||||
privacySettings: Signal<AccountPrivacySettings?, NoError>,
|
||||
hasTwoStepAuth: Signal<Bool?, NoError>,
|
||||
twoStepAuthData: Signal<TwoStepVerificationAccessConfiguration?, NoError>,
|
||||
activeSessionsContext: Signal<ActiveSessionsContext?, NoError>,
|
||||
webSessionsContext: Signal<WebSessionsContext?, NoError>
|
||||
) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise = Promise(self.presentationData)
|
||||
|
|
@ -273,15 +309,30 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
self.addSubnode(self.listNode)
|
||||
self.view.addSubview(self.edgeEffectView)
|
||||
|
||||
let interaction = SettingsSearchInteraction(openItem: { result in
|
||||
addRecentSettingsSearchItem(engine: context.engine, item: result.id)
|
||||
openResult(result)
|
||||
}, deleteRecentItem: { id in
|
||||
removeRecentSettingsSearchItem(engine: context.engine, item: id)
|
||||
})
|
||||
let interaction = SettingsSearchInteraction(
|
||||
openItem: { item in
|
||||
addRecentSettingsSearchItem(engine: context.engine, item: item.id)
|
||||
openResult(item)
|
||||
},
|
||||
openItemContextMenu: { item, node, rect, gesture in
|
||||
openContextMenu(item, node, rect, gesture)
|
||||
},
|
||||
deleteRecentItem: { id in
|
||||
removeRecentSettingsSearchItem(engine: context.engine, item: id)
|
||||
}
|
||||
)
|
||||
|
||||
let searchableItems = Promise<[SettingsSearchableItem]>()
|
||||
searchableItems.set(settingsSearchableItems(context: context, notificationExceptionsList: exceptionsList, archivedStickerPacks: archivedStickerPacks, privacySettings: privacySettings, hasTwoStepAuth: hasTwoStepAuth, twoStepAuthData: twoStepAuthData, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext))
|
||||
searchableItems.set(settingsSearchableItems(
|
||||
context: context,
|
||||
notificationExceptionsList: exceptionsList,
|
||||
archivedStickerPacks: archivedStickerPacks,
|
||||
privacySettings: privacySettings,
|
||||
hasTwoStepAuth: hasTwoStepAuth,
|
||||
twoStepAuthData: twoStepAuthData,
|
||||
activeSessionsContext: activeSessionsContext,
|
||||
webSessionsContext: webSessionsContext
|
||||
))
|
||||
|
||||
let faqItems = Promise<[SettingsSearchableItem]>()
|
||||
faqItems.set(faqSearchableItems(context: context, resolvedUrl: resolvedFaqUrl, suggestAccountDeletion: false))
|
||||
|
|
@ -294,11 +345,11 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
let results = searchSettingsItems(items: searchableItems, query: query)
|
||||
let faqResults = searchSettingsItems(items: faqSearchableItems, query: query)
|
||||
let finalResults: [SettingsSearchableItem]
|
||||
if faqResults.first?.id == .faq(1) {
|
||||
finalResults = faqResults + results
|
||||
} else {
|
||||
//if faqResults.first?.id == .faq(1) {
|
||||
// finalResults = faqResults + results
|
||||
//} else {
|
||||
finalResults = results + faqResults
|
||||
}
|
||||
//}
|
||||
return .single((query, finalResults))
|
||||
} else {
|
||||
return .single(nil)
|
||||
|
|
@ -308,12 +359,12 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
|
||||
self.recentListNode.isHidden = false
|
||||
|
||||
let previousRecentlySearchedItemOrder = Atomic<[SettingsSearchableItemId]>(value: [])
|
||||
let previousRecentlySearchedItemOrder = Atomic<[AnyHashable]>(value: [])
|
||||
let fixedRecentlySearchedItems = settingsSearchRecentItems(engine: context.engine)
|
||||
|> map { recentIds -> [SettingsSearchableItemId] in
|
||||
var result: [SettingsSearchableItemId] = []
|
||||
|> map { recentIds -> [AnyHashable] in
|
||||
var result: [AnyHashable] = []
|
||||
let _ = previousRecentlySearchedItemOrder.modify { current in
|
||||
var updated: [SettingsSearchableItemId] = []
|
||||
var updated: [AnyHashable] = []
|
||||
for id in current {
|
||||
inner: for recentId in recentIds {
|
||||
if recentId == id {
|
||||
|
|
@ -336,7 +387,7 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
|
||||
let recentSearchItems = combineLatest(searchableItems.get(), fixedRecentlySearchedItems)
|
||||
|> map { searchableItems, recentItems -> [SettingsSearchableItem] in
|
||||
let searchableItemsMap = searchableItems.reduce([SettingsSearchableItemId : SettingsSearchableItem]()) { (map, item) -> [SettingsSearchableItemId: SettingsSearchableItem] in
|
||||
let searchableItemsMap = searchableItems.reduce([AnyHashable : SettingsSearchableItem]()) { (map, item) -> [AnyHashable: SettingsSearchableItem] in
|
||||
var map = map
|
||||
map[item.id] = item
|
||||
return map
|
||||
|
|
@ -344,10 +395,10 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
var result: [SettingsSearchableItem] = []
|
||||
for itemId in recentItems {
|
||||
if let searchItem = searchableItemsMap[itemId] {
|
||||
if case let .language(id) = searchItem.id, id > 0 {
|
||||
} else {
|
||||
//if case let .language(id) = searchItem.id, id > 0 {
|
||||
//} else {
|
||||
result.append(searchItem)
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
|
@ -620,7 +671,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode {
|
|||
}
|
||||
})
|
||||
}
|
||||
}, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasTwoStepAuth: self.hasTwoStepAuth, twoStepAuthData: self.twoStepAuthData, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in
|
||||
}, openContextMenu: { _, _, _, _ in }, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasTwoStepAuth: self.hasTwoStepAuth, twoStepAuthData: self.twoStepAuthData, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in
|
||||
self?.cancel()
|
||||
}, fieldStyle: placeholderNode.fieldStyle)
|
||||
|
||||
|
|
|
|||
|
|
@ -187,14 +187,14 @@ class SettingsSearchRecentItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: subtitleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var height = titleLayout.size.height
|
||||
if subtitle.isEmpty {
|
||||
height += 22.0
|
||||
} else {
|
||||
height += 39.0
|
||||
var height: CGFloat = 30.0 + titleLayout.size.height
|
||||
if !subtitle.isEmpty {
|
||||
height += 9.0
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: height)
|
||||
let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
|
||||
|
||||
return (nodeLayout, { [weak self] in
|
||||
var updatedTheme: PresentationTheme?
|
||||
if currentItem?.theme !== item.theme {
|
||||
|
|
@ -215,7 +215,7 @@ class SettingsSearchRecentItemNode: ItemListRevealOptionsItemNode {
|
|||
let _ = titleApply()
|
||||
let _ = subtitleApply()
|
||||
|
||||
let titleY: CGFloat = subtitle.isEmpty ? 11.0 : 11.0
|
||||
let titleY: CGFloat = subtitle.isEmpty ? 16.0 - UIScreenPixel : 11.0
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleY), size: titleLayout.size)
|
||||
strongSelf.subtitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleY + titleLayout.size.height + 1.0), size: subtitleLayout.size)
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ class SettingsSearchRecentItemNode: ItemListRevealOptionsItemNode {
|
|||
let topHighlightInset: CGFloat = (firstWithHeader || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.contentSize.height + topHighlightInset))
|
||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||
strongSelf.separatorNode.isHidden = last
|
||||
|
||||
|
|
|
|||
|
|
@ -38,28 +38,34 @@ public final class RecentSettingsSearchQueryItem: Codable {
|
|||
}
|
||||
}
|
||||
|
||||
func addRecentSettingsSearchItem(engine: TelegramEngine, item: SettingsSearchableItemId) {
|
||||
let itemId = SettingsSearchRecentQueryItemId(item.index)
|
||||
let _ = engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId.rawValue, item: RecentSettingsSearchQueryItem(), removeTailIfCountExceeds: 100).start()
|
||||
func addRecentSettingsSearchItem(engine: TelegramEngine, item: AnyHashable) {
|
||||
guard let id = item.base as? String, let data = id.data(using: .ascii) else {
|
||||
return
|
||||
}
|
||||
let itemId = MemoryBuffer(data: data)
|
||||
let _ = engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId, item: RecentSettingsSearchQueryItem(), removeTailIfCountExceeds: 100).start()
|
||||
}
|
||||
|
||||
func removeRecentSettingsSearchItem(engine: TelegramEngine, item: SettingsSearchableItemId) {
|
||||
let itemId = SettingsSearchRecentQueryItemId(item.index)
|
||||
let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId.rawValue).start()
|
||||
func removeRecentSettingsSearchItem(engine: TelegramEngine, item: AnyHashable) {
|
||||
guard let id = item.base as? String, let data = id.data(using: .ascii) else {
|
||||
return
|
||||
}
|
||||
let itemId = MemoryBuffer(data: data)
|
||||
let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId).start()
|
||||
}
|
||||
|
||||
func clearRecentSettingsSearchItems(engine: TelegramEngine) {
|
||||
let _ = engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems).start()
|
||||
}
|
||||
|
||||
func settingsSearchRecentItems(engine: TelegramEngine) -> Signal<[SettingsSearchableItemId], NoError> {
|
||||
func settingsSearchRecentItems(engine: TelegramEngine) -> Signal<[AnyHashable], NoError> {
|
||||
return engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems))
|
||||
|> map { items -> [SettingsSearchableItemId] in
|
||||
var result: [SettingsSearchableItemId] = []
|
||||
|> map { items -> [AnyHashable] in
|
||||
var result: [AnyHashable] = []
|
||||
for item in items {
|
||||
let index = SettingsSearchRecentQueryItemId(item.id).value
|
||||
if let itemId = SettingsSearchableItemId(index: index) {
|
||||
result.append(itemId)
|
||||
let data = item.id.makeData()
|
||||
if let id = String(data: data, encoding: .utf8) {
|
||||
result.append(id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -77,6 +77,15 @@ private let titleFont = Font.regular(17.0)
|
|||
private let subtitleFont = Font.regular(13.0)
|
||||
|
||||
class SettingsSearchResultItemNode: ListViewItemNode {
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let extractedBackgroundImageNode: ASImageNode
|
||||
|
||||
private var extractedRect: CGRect?
|
||||
private var nonExtractedRect: CGRect?
|
||||
|
||||
private let offsetContainerNode: ASDisplayNode
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
|
@ -90,6 +99,15 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
|||
private var layoutParams: (ListViewItemLayoutParams, ItemListNeighbors)?
|
||||
|
||||
init() {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
|
||||
self.extractedBackgroundImageNode = ASImageNode()
|
||||
self.extractedBackgroundImageNode.displaysAsynchronously = false
|
||||
self.extractedBackgroundImageNode.alpha = 0.0
|
||||
|
||||
self.offsetContainerNode = ASDisplayNode()
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
|
|
@ -119,9 +137,48 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
|||
|
||||
super.init(layerBacked: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.containerNode.addSubnode(self.contextSourceNode)
|
||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode)
|
||||
self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode)
|
||||
|
||||
self.offsetContainerNode.addSubnode(self.iconNode)
|
||||
self.offsetContainerNode.addSubnode(self.titleNode)
|
||||
self.offsetContainerNode.addSubnode(self.subtitleNode)
|
||||
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
|
||||
cancelParentGestures(view: strongSelf.view)
|
||||
|
||||
item.interaction.openItemContextMenu(item.item, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture)
|
||||
}
|
||||
|
||||
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 52.0, color: item.theme.list.plainBackgroundColor)
|
||||
}
|
||||
|
||||
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||
let rect = isExtracted ? extractedRect : nonExtractedRect
|
||||
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
|
||||
}
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0))
|
||||
transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||
if !isExtracted {
|
||||
self?.extractedBackgroundImageNode.image = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: SettingsSearchResultItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
|
|
@ -132,7 +189,7 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
|||
|
||||
return { item, params, neighbors in
|
||||
var leftInset: CGFloat = params.leftInset
|
||||
let contentInset: CGFloat = 58.0
|
||||
let contentInset: CGFloat = 60.0
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
|
|
@ -157,12 +214,11 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
|||
updateIconImage = item.icon
|
||||
}
|
||||
|
||||
var height = titleLayout.size.height
|
||||
if subtitle.isEmpty {
|
||||
height += 22.0
|
||||
} else {
|
||||
height += 39.0
|
||||
var height: CGFloat = 30.0 + titleLayout.size.height
|
||||
if !subtitle.isEmpty {
|
||||
height += 9.0
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: height)
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
return (layout, { [weak self] animated in
|
||||
|
|
@ -219,11 +275,29 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
|||
default:
|
||||
bottomStripeInset = 0.0
|
||||
}
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.offsetContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
|
||||
let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
|
||||
strongSelf.extractedRect = extractedRect
|
||||
strongSelf.nonExtractedRect = nonExtractedRect
|
||||
|
||||
if strongSelf.contextSourceNode.isExtractedToContextPreview {
|
||||
strongSelf.extractedBackgroundImageNode.frame = extractedRect
|
||||
} else {
|
||||
strongSelf.extractedBackgroundImageNode.frame = nonExtractedRect
|
||||
}
|
||||
strongSelf.contextSourceNode.contentRect = extractedRect
|
||||
|
||||
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.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
let titleY: CGFloat = subtitle.isEmpty ? 11.0 : 11.0
|
||||
let titleY: CGFloat = subtitle.isEmpty ? 16.0 - UIScreenPixel : 11.0
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: titleY), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.subtitleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: titleY + titleLayout.size.height + 1.0), size: subtitleLayout.size))
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -249,13 +249,19 @@ private func archivedStickerPacksControllerEntries(context: AccountContext, mode
|
|||
return entries
|
||||
}
|
||||
|
||||
public func archivedStickerPacksController(context: AccountContext, mode: ArchivedStickerPacksControllerMode, archived: [ArchivedStickerPackItem]?, forceTheme: PresentationTheme? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void) -> ViewController {
|
||||
public func archivedStickerPacksController(context: AccountContext, mode: ArchivedStickerPacksControllerMode, archived: [ArchivedStickerPackItem]?, forceTheme: PresentationTheme? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void, forceEdit: Bool = false) -> ViewController {
|
||||
let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: ArchivedStickerPacksControllerState())
|
||||
let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
if forceEdit {
|
||||
updateState {
|
||||
$0.withUpdatedEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var navigationControllerImpl: (() -> NavigationController?)?
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,10 @@ private enum InstalledStickerPacksSection: Int32 {
|
|||
}
|
||||
|
||||
public enum InstalledStickerPacksEntryTag: ItemListItemTag {
|
||||
case edit
|
||||
case suggestOptions
|
||||
case largeEmoji
|
||||
case dynamicOrder
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? InstalledStickerPacksEntryTag, self == other {
|
||||
|
|
@ -397,7 +400,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
|
|||
case let .largeEmoji(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleLargeEmoji(value)
|
||||
})
|
||||
}, tag: InstalledStickerPacksEntryTag.largeEmoji)
|
||||
case let .trending(theme, text, count):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Trending")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge(theme.list.itemAccentColor), sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openFeatured()
|
||||
|
|
@ -421,7 +424,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
|
|||
case let .packOrder(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleDynamicPackOrder(value)
|
||||
})
|
||||
}, tag: InstalledStickerPacksEntryTag.dynamicOrder)
|
||||
case let .packOrderInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .suggestAnimatedEmoji(text, value):
|
||||
|
|
@ -651,6 +654,12 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
|||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
if focusOnItemTag == InstalledStickerPacksEntryTag.edit {
|
||||
updateState {
|
||||
$0.withUpdatedEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if let forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
|
|
@ -1299,6 +1308,20 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
|||
controller?.dismiss()
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,18 @@ import WallpaperBackgroundNode
|
|||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
public enum TextSizeSelectionEntryTag: ItemListItemTag, Equatable {
|
||||
case useSystem
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? TextSizeSelectionEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
|
@ -33,6 +45,8 @@ private func generateMaskImage(color: UIColor) -> UIImage? {
|
|||
|
||||
private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private let focusOnItemTag: TextSizeSelectionEntryTag?
|
||||
|
||||
private var presentationThemeSettings: PresentationThemeSettings
|
||||
private var presentationData: PresentationData
|
||||
|
||||
|
|
@ -57,8 +71,9 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
|||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings, dismiss: @escaping () -> Void, apply: @escaping (Bool, PresentationFontSize, PresentationFontSize) -> Void) {
|
||||
init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings, focusOnItemTag: TextSizeSelectionEntryTag?, dismiss: @escaping () -> Void, apply: @escaping (Bool, PresentationFontSize, PresentationFontSize) -> Void) {
|
||||
self.context = context
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationThemeSettings = presentationThemeSettings
|
||||
|
|
@ -92,7 +107,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
|||
self.chatBackgroundNode.update(wallpaper: self.presentationData.chatWallpaper, animated: false)
|
||||
self.chatBackgroundNode.updateBubbleTheme(bubbleTheme: self.presentationData.theme, bubbleCorners: self.presentationData.chatBubbleCorners)
|
||||
|
||||
self.toolbarNode = TextSelectionToolbarNode(presentationThemeSettings: self.presentationThemeSettings, presentationData: self.presentationData)
|
||||
self.toolbarNode = TextSelectionToolbarNode(presentationThemeSettings: self.presentationThemeSettings, presentationData: self.presentationData, focusOnItemTag: focusOnItemTag)
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.displaysAsynchronously = false
|
||||
|
|
@ -602,6 +617,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
|||
|
||||
final class TextSizeSelectionController: ViewController {
|
||||
private let context: AccountContext
|
||||
private let focusOnItemTag: TextSizeSelectionEntryTag?
|
||||
|
||||
private var controllerNode: TextSizeSelectionControllerNode {
|
||||
return self.displayNode as! TextSizeSelectionControllerNode
|
||||
|
|
@ -618,8 +634,9 @@ final class TextSizeSelectionController: ViewController {
|
|||
private var disposable: Disposable?
|
||||
private var applyDisposable = MetaDisposable()
|
||||
|
||||
public init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings) {
|
||||
public init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings, focusOnItemTag: TextSizeSelectionEntryTag? = nil) {
|
||||
self.context = context
|
||||
self.focusOnItemTag = focusOnItemTag
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationThemeSettings = presentationThemeSettings
|
||||
|
|
@ -670,7 +687,7 @@ final class TextSizeSelectionController: ViewController {
|
|||
override public func loadDisplayNode() {
|
||||
super.loadDisplayNode()
|
||||
|
||||
self.displayNode = TextSizeSelectionControllerNode(context: self.context, presentationThemeSettings: self.presentationThemeSettings, dismiss: { [weak self] in
|
||||
self.displayNode = TextSizeSelectionControllerNode(context: self.context, presentationThemeSettings: self.presentationThemeSettings, focusOnItemTag: self.focusOnItemTag, dismiss: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
|
|
@ -727,7 +744,7 @@ private final class TextSelectionToolbarNode: ASDisplayNode {
|
|||
var updateUseSystemFont: ((Bool) -> Void)?
|
||||
var updateCustomFontSize: ((PresentationFontSize) -> Void)?
|
||||
|
||||
init(presentationThemeSettings: PresentationThemeSettings, presentationData: PresentationData) {
|
||||
init(presentationThemeSettings: PresentationThemeSettings, presentationData: PresentationData, focusOnItemTag: TextSizeSelectionEntryTag?) {
|
||||
self.presentationThemeSettings = presentationThemeSettings
|
||||
self.presentationData = presentationData
|
||||
|
||||
|
|
@ -774,6 +791,10 @@ private final class TextSelectionToolbarNode: ASDisplayNode {
|
|||
|
||||
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
||||
self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
if focusOnItemTag == .useSystem {
|
||||
self.switchItemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
|
||||
func setDoneEnabled(_ enabled: Bool) {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
|
|||
case powerSaving
|
||||
case stickersAndEmoji
|
||||
case animations
|
||||
case tapForNextMedia
|
||||
case nightMode
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? ThemeSettingsEntryTag, self == other {
|
||||
|
|
@ -318,7 +320,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
case let .autoNight(_, title, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleNightTheme(value)
|
||||
}, tag: nil)
|
||||
}, tag: ThemeSettingsEntryTag.nightMode)
|
||||
case let .autoNightTheme(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.openAutoNightTheme()
|
||||
|
|
@ -352,7 +354,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
case let .showNextMediaOnTap(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleShowNextMediaOnTap(value)
|
||||
}, tag: ThemeSettingsEntryTag.animations)
|
||||
}, tag: ThemeSettingsEntryTag.tapForNextMedia)
|
||||
case let .showNextMediaOnTapInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
|
|
@ -1309,6 +1311,21 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
presentCrossfadeControllerImpl?(true)
|
||||
})
|
||||
}
|
||||
|
||||
if let focusOnItemTag {
|
||||
var didFocusOnItem = false
|
||||
controller.afterTransactionCompleted = { [weak controller] in
|
||||
if !didFocusOnItem, let controller {
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, tag.isEqual(to: focusOnItemTag) {
|
||||
didFocusOnItem = true
|
||||
itemNode.displayHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,10 +91,10 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
|
|
@ -105,7 +105,7 @@ private final class SheetContent: CombinedComponent {
|
|||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ swift_library(
|
|||
"//submodules/Pasteboard:Pasteboard",
|
||||
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -23,9 +23,13 @@ import MultiAnimationRenderer
|
|||
import Pasteboard
|
||||
import StickerPackEditTitleController
|
||||
import EntityKeyboard
|
||||
//import CameraScreen
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
import EdgeEffect
|
||||
import GlassBarButtonComponent
|
||||
import BundleIconComponent
|
||||
import LottieComponent
|
||||
import ButtonComponent
|
||||
|
||||
private let maxStickersCount = 120
|
||||
|
||||
|
|
@ -143,18 +147,20 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
private let previewIconFile: TelegramMediaFile?
|
||||
private var mainPreviewIcon: ComponentView<Empty>?
|
||||
private let gridNode: GridNode
|
||||
private let actionAreaBackgroundNode: NavigationBackgroundNode
|
||||
private let actionAreaSeparatorNode: ASDisplayNode
|
||||
private let buttonNode: HighlightableButtonNode
|
||||
private let titleBackgroundnode: NavigationBackgroundNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private var titlePlaceholderNode: ShimmerEffectNode?
|
||||
private let titleContainer: ASDisplayNode
|
||||
private let titleSeparatorNode: ASDisplayNode
|
||||
|
||||
private let topContainerNode: ASDisplayNode
|
||||
private let cancelButtonNode: HighlightableButtonNode
|
||||
private let moreButtonNode: MoreButtonNode
|
||||
private let bottomContainerNode: ASDisplayNode
|
||||
|
||||
private let topEdgeEffectView = EdgeEffectView()
|
||||
private let bottomEdgeEffectView = EdgeEffectView()
|
||||
private let button = ComponentView<Empty>()
|
||||
private let cancelButton = ComponentView<Empty>()
|
||||
private let moreButton = ComponentView<Empty>()
|
||||
private let moreButtonPlayOnce = ActionSlot<Void>()
|
||||
|
||||
private(set) var validLayout: (ContainerViewLayout, CGRect, CGFloat, UIEdgeInsets)?
|
||||
|
||||
|
|
@ -176,6 +182,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
return self.isReadyValue.get()
|
||||
}
|
||||
|
||||
private var itemCount: Int32 = 0
|
||||
|
||||
var expandProgress: CGFloat = 0.0
|
||||
var expandScrollProgress: CGFloat = 0.0
|
||||
var modalProgress: CGFloat = 0.0
|
||||
|
|
@ -225,7 +233,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = true
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 76.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
|
||||
|
||||
self.previewIconFile = previewIconFile
|
||||
if self.previewIconFile != nil {
|
||||
|
|
@ -235,14 +243,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.gridNode = GridNode()
|
||||
self.gridNode.scrollView.alwaysBounceVertical = true
|
||||
self.gridNode.scrollView.showsVerticalScrollIndicator = false
|
||||
|
||||
self.titleBackgroundnode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
||||
|
||||
self.actionAreaBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
|
||||
|
||||
self.actionAreaSeparatorNode = ASDisplayNode()
|
||||
self.actionAreaSeparatorNode.backgroundColor = self.presentationData.theme.rootController.tabBar.separatorColor
|
||||
|
||||
|
||||
self.bottomContainerNode = ASDisplayNode()
|
||||
|
||||
self.buttonNode = HighlightableButtonNode()
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.textAlignment = .center
|
||||
|
|
@ -261,13 +264,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
}
|
||||
|
||||
self.titleContainer = ASDisplayNode()
|
||||
self.titleSeparatorNode = ASDisplayNode()
|
||||
self.titleSeparatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
self.topContainerNode = ASDisplayNode()
|
||||
self.cancelButtonNode = HighlightableButtonNode()
|
||||
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
|
||||
self.moreButtonNode.iconNode.enqueueState(.more, animated: false)
|
||||
|
||||
var addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> Void)?
|
||||
var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)?
|
||||
|
|
@ -290,18 +287,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.gridNode)
|
||||
self.addSubnode(self.actionAreaBackgroundNode)
|
||||
self.addSubnode(self.actionAreaSeparatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.titleContainer.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.titleContainer)
|
||||
self.addSubnode(self.titleSeparatorNode)
|
||||
|
||||
self.addSubnode(self.topContainerNode)
|
||||
self.topContainerNode.addSubnode(self.cancelButtonNode)
|
||||
self.topContainerNode.addSubnode(self.moreButtonNode)
|
||||
|
||||
self.addSubnode(self.bottomContainerNode)
|
||||
|
||||
self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
||||
}
|
||||
|
|
@ -329,14 +321,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.gridNode.visibleContentOffsetChanged = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateButtonBackgroundAlpha()
|
||||
}
|
||||
|
||||
|
||||
self.gridNode.interactiveScrollingWillBeEnded = { [weak self] contentOffset, velocity, targetOffset -> CGPoint in
|
||||
guard let strongSelf = self, !strongSelf.isDismissed else {
|
||||
return targetOffset
|
||||
|
|
@ -431,27 +416,27 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
strongSelf.updateStickerPackContents(contents, hasPremium: hasPremium)
|
||||
})
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.buttonNode.alpha = 0.8
|
||||
} else {
|
||||
strongSelf.buttonNode.alpha = 1.0
|
||||
strongSelf.buttonNode.layer.animateAlpha(from: 0.8, to: 1.0, duration: 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
// self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
// self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
// if let strongSelf = self {
|
||||
// if highlighted {
|
||||
// strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity")
|
||||
// strongSelf.buttonNode.alpha = 0.8
|
||||
// } else {
|
||||
// strongSelf.buttonNode.alpha = 1.0
|
||||
// strongSelf.buttonNode.layer.animateAlpha(from: 0.8, to: 1.0, duration: 0.3)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
self.cancelButtonNode.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
||||
//self.cancelButtonNode.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
//self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.moreButtonNode.action = { [weak self] _, gesture in
|
||||
if let strongSelf = self {
|
||||
strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
|
||||
}
|
||||
}
|
||||
// self.moreButtonNode.action = { [weak self] _, gesture in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
|
||||
// }
|
||||
// }
|
||||
|
||||
self.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.2)
|
||||
|
||||
|
|
@ -510,7 +495,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
private var reorderingGestureRecognizer: ReorderingGestureRecognizer?
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
|
||||
let peekGestureRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in
|
||||
if let strongSelf = self {
|
||||
if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
|
||||
|
|
@ -690,6 +675,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
reorderingGestureRecognizer.isEnabled = self.isEditing
|
||||
self.reorderingGestureRecognizer = reorderingGestureRecognizer
|
||||
self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer)
|
||||
|
||||
self.topContainerNode.view.addSubview(self.topEdgeEffectView)
|
||||
self.bottomContainerNode.view.addSubview(self.bottomEdgeEffectView)
|
||||
}
|
||||
|
||||
private func hideMainPreviewIcon() {
|
||||
|
|
@ -1005,15 +993,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
|
||||
|
||||
self.titleBackgroundnode.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.actionAreaBackgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||
self.actionAreaSeparatorNode.backgroundColor = self.presentationData.theme.rootController.tabBar.separatorColor
|
||||
self.titleSeparatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
self.cancelButtonNode.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
self.moreButtonNode.theme = self.presentationData.theme
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 76.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
|
||||
|
||||
self.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
|
||||
|
||||
|
|
@ -1056,7 +1036,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.titleNode.attributedText = stringWithAppliedEntities(title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont, message: nil)
|
||||
|
||||
if let (layout, _, _, _) = self.validLayout {
|
||||
let _ = self.titleNode.updateLayout(CGSize(width: layout.size.width - max(12.0, self.cancelButtonNode.frame.width) * 2.0 - 40.0, height: .greatestFiniteMagnitude))
|
||||
let _ = self.titleNode.updateLayout(CGSize(width: layout.size.width - 44.0 * 2.0 - 40.0, height: .greatestFiniteMagnitude))
|
||||
self.updateLayout(layout: layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
|
@ -1084,7 +1064,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
private func updateIsEditing(_ isEditing: Bool) {
|
||||
self.isEditing = isEditing
|
||||
self.updateEntries(reload: true)
|
||||
self.updateButton()
|
||||
self.peekGestureRecognizer?.longPressEnabled = !isEditing
|
||||
self.reorderingGestureRecognizer?.isEnabled = isEditing
|
||||
if let (layout, _, _, _) = self.validLayout {
|
||||
|
|
@ -1096,7 +1075,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||
@objc private func morePressed(view: UIView, gesture: ContextGesture?) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
|
@ -1228,7 +1207,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
})))
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(StickerPackContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
let contextController = makeContextController(
|
||||
presentationData: presentationData,
|
||||
source: .reference(StickerPackContextReferenceContentSource(controller: controller, sourceView: view)),
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
recognizer: nil,
|
||||
gesture: gesture
|
||||
)
|
||||
self.presentInGlobalOverlay(contextController, nil)
|
||||
}
|
||||
|
||||
|
|
@ -1562,104 +1547,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
private func updateButtonBackgroundAlpha() {
|
||||
let offset = self.gridNode.visibleContentOffset()
|
||||
|
||||
let backgroundAlpha: CGFloat
|
||||
switch offset {
|
||||
case .known:
|
||||
let topPosition = self.view.convert(self.topContainerNode.frame, to: self.view).minY
|
||||
let bottomPosition = self.actionAreaBackgroundNode.view.convert(self.actionAreaBackgroundNode.bounds, to: self.view).minY
|
||||
let bottomEdgePosition = topPosition + self.topContainerNode.frame.height + self.gridNode.scrollView.contentSize.height
|
||||
let bottomOffset = bottomPosition - bottomEdgePosition
|
||||
|
||||
backgroundAlpha = min(10.0, max(0.0, -1.0 * bottomOffset)) / 10.0
|
||||
case .unknown, .none:
|
||||
backgroundAlpha = 1.0
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition
|
||||
var delay: Double = 0.0
|
||||
if backgroundAlpha >= self.actionAreaBackgroundNode.alpha || abs(backgroundAlpha - self.actionAreaBackgroundNode.alpha) < 0.01 {
|
||||
transition = .immediate
|
||||
} else {
|
||||
transition = .animated(duration: 0.2, curve: .linear)
|
||||
if abs(backgroundAlpha - self.actionAreaBackgroundNode.alpha) > 0.9 {
|
||||
delay = 0.2
|
||||
}
|
||||
}
|
||||
transition.updateAlpha(node: self.actionAreaBackgroundNode, alpha: backgroundAlpha, delay: delay)
|
||||
transition.updateAlpha(node: self.actionAreaSeparatorNode, alpha: backgroundAlpha, delay: delay)
|
||||
}
|
||||
|
||||
private func updateButton(count: Int32 = 0) {
|
||||
if let currentContents = self.currentContents, currentContents.count == 1, let content = currentContents.first, case let .result(info, _, installed) = content {
|
||||
if installed {
|
||||
let text: String
|
||||
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
|
||||
if self.isEditing {
|
||||
var updated = false
|
||||
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != self.presentationData.strings.Common_Done {
|
||||
updated = true
|
||||
}
|
||||
|
||||
if updated, let snapshotView = self.buttonNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.buttonNode.view.frame
|
||||
self.buttonNode.view.superview?.insertSubview(snapshotView, belowSubview: self.buttonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
self.buttonNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
self.buttonNode.setTitle(self.presentationData.strings.Common_Done, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(generateStretchableFilledCircleImage(radius: 11, color: self.presentationData.theme.list.itemCheckColors.fillColor), for: [])
|
||||
} else {
|
||||
let buttonTitle = self.presentationData.strings.StickerPack_EditStickers
|
||||
var updated = false
|
||||
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != buttonTitle {
|
||||
updated = true
|
||||
}
|
||||
|
||||
if updated, let snapshotView = self.buttonNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.buttonNode.view.frame
|
||||
self.buttonNode.view.superview?.insertSubview(snapshotView, belowSubview: self.buttonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
self.buttonNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
text = buttonTitle
|
||||
self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
}
|
||||
} else {
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveStickerCount(count)
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveEmojiCount(count)
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_RemoveMaskCount(count)
|
||||
}
|
||||
self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
}
|
||||
} else {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddStickerCount(count)
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddEmojiCount(count)
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_AddMaskCount(count)
|
||||
}
|
||||
self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(generateStretchableFilledCircleImage(radius: 11, color: self.presentationData.theme.list.itemCheckColors.fillColor), for: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStickerPackContents(_ contents: [LoadedStickerPack], hasPremium: Bool) {
|
||||
self.currentContents = contents
|
||||
self.didReceiveStickerPackResult = true
|
||||
|
|
@ -1864,7 +1751,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
} else {
|
||||
count = Int32(entries.count)
|
||||
}
|
||||
self.updateButton(count: count)
|
||||
self.itemCount = count
|
||||
updateLayout = true
|
||||
}
|
||||
|
||||
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) && entries.count < maxStickersCount {
|
||||
|
|
@ -2032,43 +1920,15 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
insets.top += 10.0
|
||||
}
|
||||
|
||||
var buttonHeight: CGFloat = 50.0
|
||||
var actionAreaTopInset: CGFloat = 8.0
|
||||
var actionAreaBottomInset: CGFloat = 16.0
|
||||
if let _ = self.controller?.mainActionTitle {
|
||||
|
||||
} else {
|
||||
if !self.currentStickerPacks.isEmpty {
|
||||
var installedCount = 0
|
||||
for (_, _, isInstalled) in self.currentStickerPacks {
|
||||
if isInstalled {
|
||||
installedCount += 1
|
||||
}
|
||||
}
|
||||
if installedCount == self.currentStickerPacks.count {
|
||||
buttonHeight = 42.0
|
||||
actionAreaTopInset = 1.0
|
||||
actionAreaBottomInset = 2.0
|
||||
}
|
||||
}
|
||||
if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, (!info.flags.contains(.isCreator) || info.flags.contains(.isEmoji)) {
|
||||
buttonHeight = 42.0
|
||||
actionAreaTopInset = 1.0
|
||||
actionAreaBottomInset = 2.0
|
||||
}
|
||||
}
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.intrinsicInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let titleAreaInset: CGFloat = 76.0
|
||||
let buttonHeight: CGFloat = 52.0
|
||||
let buttonSideInset: CGFloat = 30.0
|
||||
let actionAreaHeight: CGFloat = buttonHeight + buttonInsets.bottom
|
||||
|
||||
let buttonSideInset: CGFloat = 16.0
|
||||
let titleAreaInset: CGFloat = 56.0
|
||||
|
||||
var actionAreaHeight: CGFloat = buttonHeight
|
||||
actionAreaHeight += insets.bottom + actionAreaBottomInset
|
||||
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonSideInset, y: layout.size.height - actionAreaHeight + actionAreaTopInset), size: CGSize(width: layout.size.width - buttonSideInset * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: buttonHeight)))
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonSideInset, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width - buttonSideInset * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: buttonHeight)))
|
||||
|
||||
transition.updateFrame(node: self.actionAreaBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: actionAreaHeight)))
|
||||
self.actionAreaBackgroundNode.update(size: CGSize(width: layout.size.width, height: actionAreaHeight), transition: .immediate)
|
||||
transition.updateFrame(node: self.actionAreaSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.bottomContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: 90.0)))
|
||||
|
||||
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset))
|
||||
|
||||
|
|
@ -2131,17 +1991,171 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
titlePlaceholderNode.updateAbsoluteRect(titlePlaceholderNode.frame.offsetBy(dx: self.titleContainer.frame.minX, dy: self.titleContainer.frame.minY - gridInsets.top - gridFrame.minY), within: gridFrame.size)
|
||||
}
|
||||
|
||||
let cancelSize = self.cancelButtonNode.measure(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude))
|
||||
self.cancelButtonNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: 18.0), size: cancelSize)
|
||||
//let cancelSize = self.cancelButtonNode.measure(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude))
|
||||
//self.cancelButtonNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: 18.0), size: cancelSize)
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - cancelSize.width * 2.0 - 40.0, height: .greatestFiniteMagnitude))
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - 44.0 * 2.0 - 40.0, height: .greatestFiniteMagnitude))
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((-titleSize.width) / 2.0), y: floor((-titleSize.height) / 2.0)), size: titleSize)
|
||||
|
||||
self.moreButtonNode.frame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - 46.0, y: 5.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
//self.moreButtonNode.frame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - 46.0, y: 5.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
|
||||
transition.updateAlpha(node: self.cancelButtonNode, alpha: self.isEditing ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: self.moreButtonNode, alpha: self.isEditing ? 0.0 : 1.0)
|
||||
//transition.updateAlpha(node: self.cancelButtonNode, alpha: self.isEditing ? 0.0 : 1.0)
|
||||
//transition.updateAlpha(node: self.moreButtonNode, alpha: self.isEditing ? 0.0 : 1.0)
|
||||
|
||||
|
||||
let cancelButtonSize = self.cancelButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: false,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.cancelPressed()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 44.0, height: 44.0),
|
||||
)
|
||||
let cancelButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize)
|
||||
if let cancelButtonView = self.cancelButton.view {
|
||||
if cancelButtonView.superview == nil {
|
||||
self.topContainerNode.view.addSubview(cancelButtonView)
|
||||
}
|
||||
transition.updateFrame(view: cancelButtonView, frame: cancelButtonFrame)
|
||||
transition.updateAlpha(layer: cancelButtonView.layer, alpha: self.isEditing ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
let moreButtonSize = self.moreButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: false,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(
|
||||
name: "anim_morewide"
|
||||
),
|
||||
color: self.presentationData.theme.chat.inputPanel.panelControlColor,
|
||||
size: CGSize(width: 34.0, height: 34.0),
|
||||
playOnce: self.moreButtonPlayOnce
|
||||
)
|
||||
)),
|
||||
action: { [weak self] view in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.morePressed(view: view, gesture: nil)
|
||||
self.moreButtonPlayOnce.invoke(Void())
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 44.0, height: 44.0),
|
||||
)
|
||||
let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - moreButtonSize.width, y: 16.0), size: moreButtonSize)
|
||||
if let moreButtonView = self.moreButton.view {
|
||||
if moreButtonView.superview == nil {
|
||||
self.topContainerNode.view.addSubview(moreButtonView)
|
||||
}
|
||||
transition.updateFrame(view: moreButtonView, frame: moreButtonFrame)
|
||||
transition.updateAlpha(layer: moreButtonView.layer, alpha: self.isEditing ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
var buttonTitle: String = ""
|
||||
var buttonForegroundColor = self.presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
var buttonBackgroundColor = self.presentationData.theme.list.itemCheckColors.fillColor
|
||||
|
||||
if let currentContents = self.currentContents, currentContents.count == 1, let content = currentContents.first, case let .result(info, _, installed) = content {
|
||||
if installed {
|
||||
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
|
||||
if self.isEditing {
|
||||
var updated = false
|
||||
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != self.presentationData.strings.Common_Done {
|
||||
updated = true
|
||||
}
|
||||
|
||||
if updated, let snapshotView = self.buttonNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.buttonNode.view.frame
|
||||
self.buttonNode.view.superview?.insertSubview(snapshotView, belowSubview: self.buttonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
self.buttonNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
buttonTitle = self.presentationData.strings.Common_Done
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_EditStickers
|
||||
buttonBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
buttonBackgroundColor = self.presentationData.theme.list.itemAccentColor
|
||||
}
|
||||
} else {
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_RemoveStickerCount(self.itemCount)
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_RemoveEmojiCount(self.itemCount)
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_RemoveMaskCount(self.itemCount)
|
||||
}
|
||||
buttonBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
buttonForegroundColor = self.presentationData.theme.list.itemDestructiveColor
|
||||
}
|
||||
} else {
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_AddStickerCount(self.itemCount)
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_AddEmojiCount(self.itemCount)
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.StickerPack_AddMaskCount(self.itemCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: buttonBackgroundColor,
|
||||
foreground: buttonForegroundColor,
|
||||
pressedColor: buttonBackgroundColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(Text(text: buttonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))
|
||||
),
|
||||
action: { [weak self] in
|
||||
self?.buttonPressed()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: layout.size.width - buttonInsets.left - buttonInsets.right, height: 52.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: 0.0), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.bottomContainerNode.view.addSubview(buttonView)
|
||||
}
|
||||
buttonView.frame = buttonFrame
|
||||
}
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: actionAreaHeight - 90.0), size: CGSize(width: layout.size.width, height: 90.0))
|
||||
transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
|
||||
self.bottomEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
|
||||
|
||||
if firstTime {
|
||||
while !self.enqueuedTransactions.isEmpty {
|
||||
|
|
@ -2194,7 +2208,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
if !transition.isAnimated {
|
||||
self.backgroundNode.layer.removeAllAnimations()
|
||||
self.titleContainer.layer.removeAllAnimations()
|
||||
self.titleSeparatorNode.layer.removeAllAnimations()
|
||||
}
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: max(minBackgroundY, unclippedBackgroundY)), size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||
|
|
@ -2203,7 +2216,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
backgroundFrame.origin.y = min(0.0, backgroundFrame.origin.y)
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0)), size: CGSize())
|
||||
} else {
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((56.0) / 2.0)), size: CGSize())
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((56.0) / 2.0) + 10.0), size: CGSize())
|
||||
}
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
|
||||
|
|
@ -2239,14 +2252,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
}
|
||||
|
||||
transition.updateFrame(node: self.titleContainer, frame: titleContainerFrame)
|
||||
transition.updateFrame(node: self.titleSeparatorNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY + 56.0 - UIScreenPixel), size: CGSize(width: backgroundFrame.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.titleBackgroundnode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: 56.0)))
|
||||
self.titleBackgroundnode.update(size: CGSize(width: layout.size.width, height: 56.0), transition: .immediate)
|
||||
|
||||
transition.updateFrame(node: self.topContainerNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: 56.0)))
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateAlpha(node: self.titleSeparatorNode, alpha: unclippedBackgroundY < minBackgroundY ? 1.0 : 0.0)
|
||||
transition.updateFrame(node: self.topContainerNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: 76.0)))
|
||||
}
|
||||
|
||||
private func enqueueTransaction(_ transaction: StickerPackPreviewGridTransaction) {
|
||||
|
|
@ -2423,7 +2430,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
|||
let containerInsets: UIEdgeInsets
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.01)
|
||||
self.containerContainingNode.cornerRadius = 10.0
|
||||
self.containerContainingNode.cornerRadius = 38.0
|
||||
|
||||
let size = CGSize(width: 390.0, height: min(560.0, layout.size.height - 60.0))
|
||||
var contentRect: CGRect
|
||||
|
|
@ -2476,8 +2483,15 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
|||
let shadowFrame = containerContainingFrame.insetBy(dx: -60.0, dy: -60.0)
|
||||
transition.updateFrame(node: self.shadowNode, frame: shadowFrame)
|
||||
|
||||
let expandProgress: CGFloat = 1.0
|
||||
let scaledInset: CGFloat = 12.0
|
||||
let expandProgress: CGFloat
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
expandProgress = 1.0
|
||||
} else if let selectedContainer = self.containers[self.selectedStickerPackIndex] {
|
||||
expandProgress = selectedContainer.expandProgress
|
||||
} else {
|
||||
expandProgress = 0.0
|
||||
}
|
||||
let scaledInset: CGFloat = 6.0
|
||||
let scaledDistance: CGFloat = 4.0
|
||||
let minScale = (layout.size.width - scaledInset * 2.0) / layout.size.width
|
||||
let containerScale = expandProgress * 1.0 + (1.0 - expandProgress) * minScale
|
||||
|
|
@ -3101,15 +3115,15 @@ public func StickerPackScreen(
|
|||
|
||||
private final class StickerPackContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
private let sourceView: UIView
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
init(controller: ViewController, sourceView: UIView) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,6 +185,13 @@ public enum MessageActionUrlAuthResult {
|
|||
public let platform: String
|
||||
public let ip: String
|
||||
public let region: String
|
||||
|
||||
public init(browser: String, platform: String, ip: String, region: String) {
|
||||
self.browser = browser
|
||||
self.platform = platform
|
||||
self.ip = ip
|
||||
self.region = region
|
||||
}
|
||||
}
|
||||
|
||||
case `default`
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public extension TelegramEngine {
|
|||
}
|
||||
|
||||
public func requestMessageActionUrlAuth(subject: MessageActionUrlSubject) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
_internal_requestMessageActionUrlAuth(account: self.account, subject: subject)
|
||||
return _internal_requestMessageActionUrlAuth(account: self.account, subject: subject)
|
||||
}
|
||||
|
||||
public func acceptMessageActionUrlAuth(subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
|
|
|
|||
|
|
@ -3186,6 +3186,12 @@ private final class CraftGiftsContextImpl {
|
|||
return _internal_craftStarGift(account: self.account, references: references)
|
||||
}
|
||||
|
||||
func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||
self.gifts.insert(gift, at: 0)
|
||||
self.count = self.count + 1
|
||||
self.pushState()
|
||||
}
|
||||
|
||||
func removeGifts(references: [StarGiftReference]) {
|
||||
let referencesSet = Set(references)
|
||||
self.gifts.removeAll { gift in
|
||||
|
|
@ -3318,6 +3324,12 @@ public final class CraftGiftsContext {
|
|||
impl.reload()
|
||||
}
|
||||
}
|
||||
|
||||
public func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||
self.impl.with { impl in
|
||||
impl.addGift(gift: gift)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeGifts(references: [StarGiftReference]) {
|
||||
self.impl.with { impl in
|
||||
|
|
@ -3838,6 +3850,7 @@ private final class ResaleGiftsContextImpl {
|
|||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let giftId: Int64
|
||||
private let forCrafting: Bool
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
|
|
@ -3861,11 +3874,13 @@ private final class ResaleGiftsContextImpl {
|
|||
init(
|
||||
queue: Queue,
|
||||
account: Account,
|
||||
giftId: Int64
|
||||
giftId: Int64,
|
||||
forCrafting: Bool
|
||||
) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.giftId = giftId
|
||||
self.forCrafting = forCrafting
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
|
@ -3898,6 +3913,10 @@ private final class ResaleGiftsContextImpl {
|
|||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
if self.forCrafting {
|
||||
flags |= (1 << 4)
|
||||
}
|
||||
|
||||
switch sorting {
|
||||
case .date:
|
||||
break
|
||||
|
|
@ -4169,11 +4188,12 @@ public final class ResaleGiftsContext {
|
|||
|
||||
public init(
|
||||
account: Account,
|
||||
giftId: Int64
|
||||
giftId: Int64,
|
||||
forCrafting: Bool
|
||||
) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return ResaleGiftsContextImpl(queue: queue, account: account, giftId: giftId)
|
||||
return ResaleGiftsContextImpl(queue: queue, account: account, giftId: giftId, forCrafting: forCrafting)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||
case voiceMessagesResumeTrimWarning = 82
|
||||
case globalPostsSearch = 83
|
||||
case giftAuctionTips = 84
|
||||
case giftCraftingTips = 85
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
|
|
@ -574,6 +575,10 @@ private struct ApplicationSpecificNoticeKeys {
|
|||
static func giftAuctionTips() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftAuctionTips.key)
|
||||
}
|
||||
|
||||
static func giftCraftingTips() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftCraftingTips.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
|
|
@ -2514,4 +2519,31 @@ public struct ApplicationSpecificNotice {
|
|||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func getGiftCraftingTips(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||
return accountManager.transaction { transaction -> Int32 in
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.giftCraftingTips())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
return value.value
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func incrementGiftCraftingTips(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
|
||||
return accountManager.transaction { transaction -> Int in
|
||||
var currentValue: Int32 = 0
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.giftCraftingTips())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
currentValue = value.value
|
||||
}
|
||||
let previousValue = currentValue
|
||||
currentValue += Int32(count)
|
||||
|
||||
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.giftCraftingTips(), entry)
|
||||
}
|
||||
|
||||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -555,6 +555,10 @@ public final class PresentationThemeList {
|
|||
public func withUpdated(blocksBackgroundColor: UIColor? = nil, modalBlocksBackgroundColor: UIColor? = nil, plainBackgroundColor: UIColor? = nil, modalPlainBackgroundColor: UIColor? = nil, itemPrimaryTextColor: UIColor? = nil, itemSecondaryTextColor: UIColor? = nil, itemDisabledTextColor: UIColor? = nil, itemAccentColor: UIColor? = nil, itemHighlightedColor: UIColor? = nil, itemDestructiveColor: UIColor? = nil, itemPlaceholderTextColor: UIColor? = nil, itemBlocksBackgroundColor: UIColor? = nil, itemModalBlocksBackgroundColor: UIColor? = nil, itemHighlightedBackgroundColor: UIColor? = nil, itemBlocksSeparatorColor: UIColor? = nil, itemPlainSeparatorColor: UIColor? = nil, disclosureArrowColor: UIColor? = nil, sectionHeaderTextColor: UIColor? = nil, freeTextColor: UIColor? = nil, freeTextErrorColor: UIColor? = nil, freeTextSuccessColor: UIColor? = nil, freeMonoIconColor: UIColor? = nil, itemSwitchColors: PresentationThemeSwitch? = nil, itemDisclosureActions: PresentationThemeItemDisclosureActions? = nil, itemCheckColors: PresentationThemeFillStrokeForeground? = nil, controlSecondaryColor: UIColor? = nil, freeInputField: PresentationInputFieldTheme? = nil, freePlainInputField: PresentationInputFieldTheme? = nil, mediaPlaceholderColor: UIColor? = nil, scrollIndicatorColor: UIColor? = nil, pageIndicatorInactiveColor: UIColor? = nil, inputClearButtonColor: UIColor? = nil, itemBarChart: PresentationThemeItemBarChart? = nil, itemInputField: PresentationInputFieldTheme? = nil, paymentOption: PaymentOption? = nil) -> PresentationThemeList {
|
||||
return PresentationThemeList(blocksBackgroundColor: blocksBackgroundColor ?? self.blocksBackgroundColor, modalBlocksBackgroundColor: modalBlocksBackgroundColor ?? self.modalBlocksBackgroundColor, plainBackgroundColor: plainBackgroundColor ?? self.plainBackgroundColor, modalPlainBackgroundColor: modalPlainBackgroundColor ?? self.modalPlainBackgroundColor, itemPrimaryTextColor: itemPrimaryTextColor ?? self.itemPrimaryTextColor, itemSecondaryTextColor: itemSecondaryTextColor ?? self.itemSecondaryTextColor, itemDisabledTextColor: itemDisabledTextColor ?? self.itemDisabledTextColor, itemAccentColor: itemAccentColor ?? self.itemAccentColor, itemHighlightedColor: itemHighlightedColor ?? self.itemHighlightedColor, itemDestructiveColor: itemDestructiveColor ?? self.itemDestructiveColor, itemPlaceholderTextColor: itemPlaceholderTextColor ?? self.itemPlaceholderTextColor, itemBlocksBackgroundColor: itemBlocksBackgroundColor ?? self.itemBlocksBackgroundColor, itemModalBlocksBackgroundColor: itemModalBlocksBackgroundColor ?? self.itemModalBlocksBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, itemBlocksSeparatorColor: itemBlocksSeparatorColor ?? self.itemBlocksSeparatorColor, itemPlainSeparatorColor: itemPlainSeparatorColor ?? self.itemPlainSeparatorColor, disclosureArrowColor: disclosureArrowColor ?? self.disclosureArrowColor, sectionHeaderTextColor: sectionHeaderTextColor ?? self.sectionHeaderTextColor, freeTextColor: freeTextColor ?? self.freeTextColor, freeTextErrorColor: freeTextErrorColor ?? self.freeTextErrorColor, freeTextSuccessColor: freeTextSuccessColor ?? self.freeTextSuccessColor, freeMonoIconColor: freeMonoIconColor ?? self.freeMonoIconColor, itemSwitchColors: itemSwitchColors ?? self.itemSwitchColors, itemDisclosureActions: itemDisclosureActions ?? self.itemDisclosureActions, itemCheckColors: itemCheckColors ?? self.itemCheckColors, controlSecondaryColor: controlSecondaryColor ?? self.controlSecondaryColor, freeInputField: freeInputField ?? self.freeInputField, freePlainInputField: freePlainInputField ?? self.freePlainInputField, mediaPlaceholderColor: mediaPlaceholderColor ?? self.mediaPlaceholderColor, scrollIndicatorColor: scrollIndicatorColor ?? self.scrollIndicatorColor, pageIndicatorInactiveColor: pageIndicatorInactiveColor ?? self.pageIndicatorInactiveColor, inputClearButtonColor: inputClearButtonColor ?? self.inputClearButtonColor, itemBarChart: itemBarChart ?? self.itemBarChart, itemInputField: itemInputField ?? self.itemInputField, paymentOption: paymentOption ?? self.paymentOption)
|
||||
}
|
||||
|
||||
public var itemSearchHighlightColor: UIColor {
|
||||
return self.itemAccentColor.withMultipliedAlpha(0.2)
|
||||
}
|
||||
}
|
||||
|
||||
public final class PresentationThemeArchiveAvatarColors {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
|
|||
}
|
||||
|
||||
public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
|
||||
var balanceText = presentationStringsFormattedNumber(Int32(amount.value), dateTimeFormat.groupingSeparator)
|
||||
var balanceText = presentationStringsFormattedNumber(Int32(clamping: amount.value), dateTimeFormat.groupingSeparator)
|
||||
let fraction = abs(Double(amount.nanos)) / 10e6
|
||||
if fraction > 0.0 {
|
||||
balanceText.append(dateTimeFormat.decimalSeparator)
|
||||
|
|
|
|||
|
|
@ -465,6 +465,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftStoreScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftCraftScreen",
|
||||
"//submodules/TelegramUI/Components/ContentReportScreen",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
|
|
@ -513,7 +514,9 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AlertComponent",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController",
|
||||
"//submodules/TelegramUI/Components/CocoonInfoScreen",
|
||||
"//submodules/TelegramUI/Components/ProxyServerPreviewScreen",
|
||||
"//submodules/TelegramUI/Components/ContextControllerImpl",
|
||||
"//submodules/TelegramUI/Components/AuthConfirmationScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
|
|
|||
|
|
@ -489,10 +489,10 @@ private final class RecentActionsSettingsSheetComponent: Component {
|
|||
let leftButtonSize = self.leftButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
|
|
@ -507,7 +507,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
|
|||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 16.0), size: leftButtonSize)
|
||||
if let leftButtonView = self.leftButton.view {
|
||||
|
|
|
|||
|
|
@ -547,11 +547,12 @@ private final class AlertScreenComponent: Component {
|
|||
self.containerView.update(size: availableSize, isDark: environment.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
var availableHeight = availableSize.height
|
||||
availableHeight -= environment.statusBarHeight
|
||||
if component.configuration.allowInputInset, environment.inputHeight > 0.0 {
|
||||
availableHeight -= environment.inputHeight
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - alertSize.width) / 2.0), y: floorToScreenPixels((availableHeight - alertSize.height) / 2.0)), size: alertSize))
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - alertSize.width) / 2.0), y: environment.statusBarHeight + floorToScreenPixels((availableHeight - alertSize.height) / 2.0)), size: alertSize))
|
||||
self.backgroundView.update(size: alertSize, cornerRadius: 35.0, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||
|
||||
return availableSize
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AuthConfirmationScreen",
|
||||
module_name = "AuthConfirmationScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/Components/SheetComponent",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramUI/Components/AvatarComponent",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/PhoneNumberFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,588 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import MultilineTextComponent
|
||||
import GlassBarButtonComponent
|
||||
import ButtonComponent
|
||||
import PresentationDataUtils
|
||||
import BundleIconComponent
|
||||
import ListSectionComponent
|
||||
import ListActionItemComponent
|
||||
import AvatarComponent
|
||||
import Markdown
|
||||
import PhoneNumberFormat
|
||||
|
||||
private final class AuthConfirmationSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let subject: MessageActionUrlAuthResult
|
||||
let completion: (Bool, Bool) -> Void
|
||||
let cancel: (Bool) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
subject: MessageActionUrlAuthResult,
|
||||
completion: @escaping (Bool, Bool) -> Void,
|
||||
cancel: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
self.completion = completion
|
||||
self.cancel = cancel
|
||||
}
|
||||
|
||||
static func ==(lhs: AuthConfirmationSheetContent, rhs: AuthConfirmationSheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
private let subject: MessageActionUrlAuthResult
|
||||
|
||||
fileprivate var inProgress = false
|
||||
var allowWrite = true
|
||||
weak var controller: ViewController?
|
||||
|
||||
init(context: AccountContext, subject: MessageActionUrlAuthResult) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func displayPhoneNumberConfirmation(commit: @escaping (Bool) -> Void) {
|
||||
guard case let .request(domain, _, _, _) = self.subject else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, case let .user(user) = peer, let phone = user.phone else {
|
||||
return
|
||||
}
|
||||
let phoneNumber = formatPhoneNumber(context: self.context, number: phone)
|
||||
|
||||
//TODO:localize
|
||||
let alertController = textAlertController(
|
||||
context: self.context,
|
||||
title: "Phone Number",
|
||||
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**. Allow access?",
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: "Deny", action: {
|
||||
commit(false)
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: "Allow", action: {
|
||||
commit(true)
|
||||
})
|
||||
]
|
||||
)
|
||||
self.controller?.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, subject: self.subject)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let avatar = Child(AvatarComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
||||
let clientSection = Child(ListSectionComponent.self)
|
||||
let optionsSection = Child(ListSectionComponent.self)
|
||||
|
||||
let cancelButton = Child(ButtonComponent.self)
|
||||
let doneButton = Child(ButtonComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let component = context.component
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
let state = context.state
|
||||
if state.controller == nil {
|
||||
state.controller = environment.controller()
|
||||
}
|
||||
|
||||
let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = strings
|
||||
|
||||
guard case let .request(domain, bot, clientData, flags) = component.subject else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
component.cancel(true)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
var contentHeight: CGFloat = 32.0
|
||||
let avatar = avatar.update(
|
||||
component: AvatarComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
peer: EnginePeer(bot),
|
||||
clipStyle: .roundedRect
|
||||
),
|
||||
availableSize: CGSize(width: 92.0, height: 92.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(avatar
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + avatar.size.height / 2.0))
|
||||
)
|
||||
contentHeight += avatar.size.height
|
||||
contentHeight += 18.0
|
||||
|
||||
let titleFont = Font.bold(24.0)
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: "Log in to **\(domain)**", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 2
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + title.size.height / 2.0))
|
||||
)
|
||||
contentHeight += title.size.height
|
||||
contentHeight += 16.0
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let description = description.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: "This site will receive your **name**,\n**username** and **profile photo**.", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 3,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(description
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + description.size.height / 2.0))
|
||||
)
|
||||
contentHeight += description.size.height
|
||||
contentHeight += 16.0
|
||||
|
||||
var clientSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
clientSectionItems.append(
|
||||
AnyComponentWithIdentity(id: "device", component: AnyComponent(
|
||||
ListActionItemComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Device",
|
||||
font: Font.regular(17.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
contentInsets: UIEdgeInsets(top: 19.0, left: 0.0, bottom: 19.0, right: 0.0),
|
||||
accessory: .custom(ListActionItemComponent.CustomAccessory(
|
||||
component: AnyComponentWithIdentity(
|
||||
id: "info",
|
||||
component: AnyComponent(
|
||||
VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: clientData?.platform ?? "",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: clientData?.browser ?? "",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 15.0),
|
||||
textColor: theme.list.itemSecondaryTextColor
|
||||
)),
|
||||
horizontalAlignment: .left,
|
||||
truncationType: .middle,
|
||||
maximumNumberOfLines: 1
|
||||
)))
|
||||
], alignment: .right, spacing: 3.0)
|
||||
)
|
||||
),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 14.0),
|
||||
isInteractive: true
|
||||
)),
|
||||
action: nil
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
clientSectionItems.append(
|
||||
AnyComponentWithIdentity(id: "region", component: AnyComponent(
|
||||
ListActionItemComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "IP Address",
|
||||
font: Font.regular(17.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
contentInsets: UIEdgeInsets(top: 19.0, left: 0.0, bottom: 19.0, right: 0.0),
|
||||
accessory: .custom(ListActionItemComponent.CustomAccessory(
|
||||
component: AnyComponentWithIdentity(
|
||||
id: "info",
|
||||
component: AnyComponent(
|
||||
VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: clientData?.ip ?? "",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: clientData?.region ?? "",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 15.0),
|
||||
textColor: theme.list.itemSecondaryTextColor
|
||||
)),
|
||||
horizontalAlignment: .left,
|
||||
truncationType: .middle,
|
||||
maximumNumberOfLines: 1
|
||||
)))
|
||||
], alignment: .right, spacing: 3.0)
|
||||
)
|
||||
),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 14.0),
|
||||
isInteractive: true
|
||||
)),
|
||||
action: nil
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
let clientSection = clientSection.update(
|
||||
component: ListSectionComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
header: nil,
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "This login attempt came from the device above.",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
items: clientSectionItems
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(clientSection
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + clientSection.size.height / 2.0))
|
||||
)
|
||||
contentHeight += clientSection.size.height
|
||||
|
||||
if flags.contains(.requestWriteAccess) {
|
||||
contentHeight += 22.0
|
||||
|
||||
var optionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
optionsSectionItems.append(AnyComponentWithIdentity(id: "allowWrite", component: AnyComponent(ListActionItemComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Allow Messages",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: state.allowWrite, action: { [weak state] _ in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.allowWrite = !state.allowWrite
|
||||
state.updated()
|
||||
})),
|
||||
action: nil
|
||||
))))
|
||||
let optionsSection = optionsSection.update(
|
||||
component: ListSectionComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
header: nil,
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "This will allow \(EnginePeer(bot).compactDisplayTitle) to message you.",
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
items: optionsSectionItems
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(optionsSection
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + optionsSection.size.height / 2.0))
|
||||
)
|
||||
contentHeight += optionsSection.size.height
|
||||
}
|
||||
contentHeight += 32.0
|
||||
|
||||
let buttonSpacing: CGFloat = 10.0
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let buttonWidth = (context.availableSize.width - buttonInsets.left - buttonInsets.right - buttonSpacing) / 2.0
|
||||
|
||||
let cancelButton = cancelButton.update(
|
||||
component: ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.1),
|
||||
foreground: theme.list.itemPrimaryTextColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Cancel", font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center))))
|
||||
),
|
||||
action: {
|
||||
component.cancel(true)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: buttonWidth, height: 52.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(cancelButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0 - buttonSpacing / 2.0 - cancelButton.size.width / 2.0, y: contentHeight + cancelButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
let doneButton = doneButton.update(
|
||||
component: ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0,
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Log In", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
),
|
||||
displaysProgress: state.inProgress,
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
var allowWrite = false
|
||||
if flags.contains(.requestWriteAccess) && state.allowWrite {
|
||||
allowWrite = true
|
||||
}
|
||||
if flags.contains(.requestPhoneNumber) {
|
||||
state.displayPhoneNumberConfirmation(commit: { sharePhoneNumber in
|
||||
component.completion(allowWrite, sharePhoneNumber)
|
||||
state.inProgress = true
|
||||
state.updated()
|
||||
})
|
||||
} else {
|
||||
component.completion(allowWrite, false)
|
||||
state.inProgress = true
|
||||
state.updated()
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: buttonWidth, height: 52.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(doneButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0 + buttonSpacing / 2.0 + doneButton.size.width / 2.0, y: contentHeight + doneButton.size.height / 2.0))
|
||||
)
|
||||
contentHeight += doneButton.size.height
|
||||
contentHeight += buttonInsets.bottom
|
||||
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class AuthConfirmationSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let subject: MessageActionUrlAuthResult
|
||||
let completion: (Bool, Bool) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
subject: MessageActionUrlAuthResult,
|
||||
completion: @escaping (Bool, Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
static func ==(lhs: AuthConfirmationSheetComponent, rhs: AuthConfirmationSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(SheetComponent<EnvironmentType>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(AuthConfirmationSheetContent(
|
||||
context: context.component.context,
|
||||
subject: context.component.subject,
|
||||
completion: context.component.completion,
|
||||
cancel: { animate in
|
||||
if animate {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else if let controller = controller() {
|
||||
controller.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
}
|
||||
)),
|
||||
style: .glass,
|
||||
backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
clipsContent: true,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthConfirmationScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let subject: MessageActionUrlAuthResult
|
||||
fileprivate let completion: (Bool, Bool) -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
subject: MessageActionUrlAuthResult,
|
||||
completion: @escaping (Bool, Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
self.completion = completion
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: AuthConfirmationSheetComponent(
|
||||
context: context,
|
||||
subject: subject,
|
||||
completion: completion
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,20 +8,28 @@ import AvatarNode
|
|||
import AccountContext
|
||||
|
||||
public final class AvatarComponent: Component {
|
||||
public enum ClipStyle {
|
||||
case round
|
||||
case roundedRect
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
let clipStyle: ClipStyle
|
||||
let icon: AnyComponent<Empty>?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
peer: EnginePeer,
|
||||
clipStyle: ClipStyle = .round,
|
||||
icon: AnyComponent<Empty>? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
self.clipStyle = clipStyle
|
||||
self.icon = icon
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +43,9 @@ public final class AvatarComponent: Component {
|
|||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.clipStyle != rhs.clipStyle {
|
||||
return false
|
||||
}
|
||||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
|
|
@ -81,7 +92,7 @@ public final class AvatarComponent: Component {
|
|||
transition: .immediate,
|
||||
component: icon,
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
containerSize: CGSize(width: 24.0, height: 24.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - iconSize.width + 2.0, y: availableSize.height - iconSize.height + 2.0), size: iconSize)
|
||||
if let iconView = iconView.view {
|
||||
|
|
@ -93,11 +104,17 @@ public final class AvatarComponent: Component {
|
|||
cutoutRect = CGRect(origin: CGPoint(x: iconFrame.minX, y: availableSize.height - iconFrame.maxY), size: iconFrame.size).insetBy(dx: -2.0 + UIScreenPixel, dy: -2.0 + UIScreenPixel)
|
||||
}
|
||||
|
||||
var clipStyle: AvatarNodeClipStyle = .round
|
||||
if case .roundedRect = component.clipStyle {
|
||||
clipStyle = .roundedRect
|
||||
}
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: .zero, size: availableSize)
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
peer: component.peer,
|
||||
clipStyle: clipStyle,
|
||||
synchronousLoad: true,
|
||||
cutoutRect: cutoutRect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AvatarBackground",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/PremiumAlertController",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import AvatarBackground
|
|||
import LottieComponent
|
||||
import UndoUI
|
||||
import PremiumAlertController
|
||||
import GlassBarButtonComponent
|
||||
import BundleIconComponent
|
||||
|
||||
public struct AvatarKeyboardInputData: Equatable {
|
||||
var emoji: EmojiPagerContentComponent
|
||||
|
|
@ -837,50 +839,63 @@ final class AvatarEditorScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
let backgroundIsBright = UIColor(rgb: state.selectedBackground.colors.first ?? 0).lightness > 0.8
|
||||
//let backgroundIsBright = UIColor(rgb: state.selectedBackground.colors.first ?? 0).lightness > 0.8
|
||||
|
||||
//state.expanded && !backgroundIsBright ? .white : environment.theme.rootController.navigationBar.accentTextColor
|
||||
let navigationCancelButtonSize = self.navigationCancelButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: state.expanded && !backgroundIsBright ? .white : environment.theme.rootController.navigationBar.accentTextColor)),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
component: AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(BundleIconComponent(name: "Navigation/Close", tintColor: environment.theme.chat.inputPanel.panelControlColor))),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?()?.dismiss()
|
||||
}
|
||||
self.controller?()?.dismiss()
|
||||
}
|
||||
).minSize(CGSize(width: 16.0, height: environment.navigationHeight - environment.statusBarHeight))),
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: environment.navigationHeight - environment.statusBarHeight)
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
if let navigationCancelButtonView = self.navigationCancelButton.view {
|
||||
if navigationCancelButtonView.superview == nil {
|
||||
self.addSubview(navigationCancelButtonView)
|
||||
}
|
||||
transition.setFrame(view: navigationCancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: environment.statusBarHeight), size: navigationCancelButtonSize))
|
||||
transition.setFrame(view: navigationCancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 16.0), size: navigationCancelButtonSize))
|
||||
transition.setAlpha(view: navigationCancelButtonView, alpha: !state.editingColor ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
let navigationDoneButtonSize = self.navigationDoneButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Text(text: component.peerType == .suggest ? strings.AvatarEditor_Suggest : strings.AvatarEditor_Set, font: Font.semibold(17.0), color: state.expanded && !backgroundIsBright ? .white : environment.theme.rootController.navigationBar.accentTextColor)),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
component: AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "done", component: AnyComponent(BundleIconComponent(name: "Navigation/Done", tintColor: environment.theme.list.itemCheckColors.foregroundColor))),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.complete()
|
||||
}
|
||||
self.complete()
|
||||
}
|
||||
).minSize(CGSize(width: 16.0, height: environment.navigationHeight - environment.statusBarHeight))),
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: environment.navigationHeight - environment.statusBarHeight)
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
if let navigationDoneButtonView = self.navigationDoneButton.view {
|
||||
if navigationDoneButtonView.superview == nil {
|
||||
self.addSubview(navigationDoneButtonView)
|
||||
}
|
||||
|
||||
transition.setFrame(view: navigationDoneButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - navigationDoneButtonSize.width, y: environment.statusBarHeight), size: navigationDoneButtonSize))
|
||||
transition.setFrame(view: navigationDoneButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - navigationDoneButtonSize.width, y: 16.0), size: navigationDoneButtonSize))
|
||||
transition.setAlpha(view: navigationDoneButtonView, alpha: (state.expanded || environment.inputHeight > 0.0) && !state.editingColor ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
|
|
@ -1190,9 +1205,10 @@ final class AvatarEditorScreenComponent: Component {
|
|||
contentHeight += keyboardTitleSize.height
|
||||
contentHeight += 8.0
|
||||
|
||||
var bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom : 16.0
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
var bottomInset: CGFloat = buttonInsets.bottom
|
||||
if !effectiveIsExpanded {
|
||||
bottomInset += 50.0 + 16.0
|
||||
bottomInset += 52.0 + buttonInsets.bottom - 14.0
|
||||
}
|
||||
|
||||
let keyboardContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height - contentHeight - bottomInset))
|
||||
|
|
@ -1318,7 +1334,6 @@ final class AvatarEditorScreenComponent: Component {
|
|||
))))
|
||||
}
|
||||
|
||||
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let buttonSize = self.buttonView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
|
|
@ -1340,13 +1355,13 @@ final class AvatarEditorScreenComponent: Component {
|
|||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - bottomInsets.left - bottomInsets.right, height: 52.0)
|
||||
containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
|
||||
)
|
||||
if let buttonView = self.buttonView.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: bottomInsets.left, y: contentHeight), size: buttonSize))
|
||||
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize))
|
||||
}
|
||||
|
||||
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight - 4.0), size: CGSize(width: availableSize.width, height: availableSize.height - contentHeight + 4.0))
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@ let collageGrids: [Camera.CollageGrid] = [
|
|||
Camera.CollageGrid(rows: [Camera.CollageGrid.Row(columns: 2), Camera.CollageGrid.Row(columns: 2), Camera.CollageGrid.Row(columns: 2)])
|
||||
]
|
||||
|
||||
enum CameraMode: Int32, Equatable {
|
||||
case photo
|
||||
case video
|
||||
case live
|
||||
}
|
||||
|
||||
struct CameraState: Equatable {
|
||||
enum CameraMode: Int32, Equatable {
|
||||
case photo
|
||||
case video
|
||||
case live
|
||||
}
|
||||
|
||||
enum Recording: Equatable {
|
||||
case none
|
||||
case holding
|
||||
|
|
@ -548,7 +548,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||
self.buttonPressTimestamp = nil
|
||||
}
|
||||
|
||||
func updateCameraMode(_ mode: CameraMode) {
|
||||
func updateCameraMode(_ mode: CameraState.CameraMode) {
|
||||
guard let controller = self.getController(), let camera = controller.camera else {
|
||||
return
|
||||
}
|
||||
|
|
@ -2102,7 +2102,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||
availableModeControlSize = availableSize
|
||||
}
|
||||
|
||||
var availableModes: [CameraMode] = [.photo, .video]
|
||||
var availableModes: [CameraState.CameraMode] = [.photo, .video]
|
||||
if !isTablet && state.canLivestream {
|
||||
availableModes.append(.live)
|
||||
}
|
||||
|
|
@ -2218,6 +2218,12 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
case avatar
|
||||
}
|
||||
|
||||
public enum CameraMode {
|
||||
case photo
|
||||
case video
|
||||
case live
|
||||
}
|
||||
|
||||
public enum PIPPosition: Int32 {
|
||||
case topLeft
|
||||
case topRight
|
||||
|
|
@ -2528,8 +2534,18 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
self.mainPreviewView.resetPlaceholder(front: cameraFrontPosition)
|
||||
}
|
||||
|
||||
let cameraMode: CameraState.CameraMode
|
||||
switch controller.cameraMode {
|
||||
case .photo:
|
||||
cameraMode = .photo
|
||||
case .video:
|
||||
cameraMode = .video
|
||||
case .live:
|
||||
cameraMode = .live
|
||||
}
|
||||
|
||||
self.cameraState = CameraState(
|
||||
mode: .photo,
|
||||
mode: cameraMode,
|
||||
position: cameraFrontPosition ? .front : .back,
|
||||
flashMode: .off,
|
||||
flashModeDidChange: false,
|
||||
|
|
@ -4002,6 +4018,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
|
||||
private let context: AccountContext
|
||||
fileprivate let mode: Mode
|
||||
fileprivate let cameraMode: CameraMode
|
||||
fileprivate let customTarget: EnginePeer.Id?
|
||||
fileprivate let resumeLiveStream: Bool
|
||||
fileprivate let holder: CameraHolder?
|
||||
|
|
@ -4071,6 +4088,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
public init(
|
||||
context: AccountContext,
|
||||
mode: Mode,
|
||||
cameraMode: CameraMode = .photo,
|
||||
customTarget: EnginePeer.Id? = nil,
|
||||
resumeLiveStream: Bool = false,
|
||||
holder: CameraHolder? = nil,
|
||||
|
|
@ -4080,6 +4098,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.cameraMode = cameraMode
|
||||
self.customTarget = customTarget
|
||||
self.resumeLiveStream = resumeLiveStream
|
||||
self.holder = holder
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import GlassBackgroundComponent
|
|||
import LiquidLens
|
||||
import TabSelectionRecognizer
|
||||
|
||||
extension CameraMode {
|
||||
extension CameraState.CameraMode {
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .photo:
|
||||
|
|
@ -28,18 +28,18 @@ final class ModeComponent: Component {
|
|||
let isTablet: Bool
|
||||
let strings: PresentationStrings
|
||||
let tintColor: UIColor
|
||||
let availableModes: [CameraMode]
|
||||
let currentMode: CameraMode
|
||||
let updatedMode: (CameraMode) -> Void
|
||||
let availableModes: [CameraState.CameraMode]
|
||||
let currentMode: CameraState.CameraMode
|
||||
let updatedMode: (CameraState.CameraMode) -> Void
|
||||
let tag: AnyObject?
|
||||
|
||||
init(
|
||||
isTablet: Bool,
|
||||
strings: PresentationStrings,
|
||||
tintColor: UIColor,
|
||||
availableModes: [CameraMode],
|
||||
currentMode: CameraMode,
|
||||
updatedMode: @escaping (CameraMode) -> Void,
|
||||
availableModes: [CameraState.CameraMode],
|
||||
currentMode: CameraState.CameraMode,
|
||||
updatedMode: @escaping (CameraState.CameraMode) -> Void,
|
||||
tag: AnyObject?
|
||||
) {
|
||||
self.isTablet = isTablet
|
||||
|
|
|
|||
|
|
@ -1428,8 +1428,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
break
|
||||
case .theme:
|
||||
break
|
||||
case .settings:
|
||||
break
|
||||
case .premiumOffer:
|
||||
break
|
||||
case .starsTopup:
|
||||
|
|
@ -1465,6 +1463,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
break
|
||||
case .sendGift:
|
||||
break
|
||||
case .chats, .contacts, .compose, .postStory, .settings, .unknownDeepLink, .oauth:
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -443,6 +443,10 @@ public final class ChatListHeaderComponent: Component {
|
|||
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0))
|
||||
}
|
||||
|
||||
func openEmojiStatusSetup() {
|
||||
self.chatListTitleView?.openEmojiStatusSetup()
|
||||
}
|
||||
|
||||
func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, displayBackButton: Bool, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) {
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
|
||||
|
||||
|
|
@ -815,6 +819,14 @@ public final class ChatListHeaderComponent: Component {
|
|||
private func updateContentStoryOffsets(transition: ComponentTransition) {
|
||||
}
|
||||
|
||||
func openEmojiStatusSetup() {
|
||||
if let storyPeerListView = self.storyPeerList?.view as? StoryPeerListComponent.View {
|
||||
storyPeerListView.openEmojiStatusSetup()
|
||||
} else {
|
||||
self.primaryContentView?.openEmojiStatusSetup()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: ChatListHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.state = state
|
||||
|
||||
|
|
|
|||
|
|
@ -615,6 +615,12 @@ public final class ChatListNavigationBar: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public func openEmojiStatusSetup() {
|
||||
if let headerContentView = self.headerContent.view as? ChatListHeaderComponent.View {
|
||||
headerContentView.openEmojiStatusSetup()
|
||||
}
|
||||
}
|
||||
|
||||
public func updateStoryUploadProgress(storyUploadProgress: [EnginePeer.Id: Float]) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -172,10 +172,10 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
|||
particleColor: statusParticleColor,
|
||||
isVisibleForAnimations: true,
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self, let titleCredibilityIconView = strongSelf.titleCredibilityIconView else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openStatusSetup?(titleCredibilityIconView)
|
||||
self.openEmojiStatusSetup()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -327,6 +327,13 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
|||
}
|
||||
}
|
||||
|
||||
public func openEmojiStatusSetup() {
|
||||
guard let titleCredibilityIconView = self.titleCredibilityIconView else {
|
||||
return
|
||||
}
|
||||
self.openStatusSetup?(titleCredibilityIconView)
|
||||
}
|
||||
|
||||
public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let _ = self.updateLayoutInternal(size: availableSize, transition: transition)
|
||||
return availableSize
|
||||
|
|
@ -414,10 +421,10 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
|||
content: statusContent,
|
||||
isVisibleForAnimations: true,
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self, let titleCredibilityIconView = strongSelf.titleCredibilityIconView else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openStatusSetup?(titleCredibilityIconView)
|
||||
self.openEmojiStatusSetup()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
|
|||
|
|
@ -167,15 +167,15 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
|||
var contentHeight: CGFloat = 0.0
|
||||
contentHeight += 30.0
|
||||
|
||||
let barButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||
let cancelSize = self.cancel.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: barButtonSize,
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
backgroundColor: nil,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
|
||||
fileprivate let playButtonAnimation = ActionSlot<Void>()
|
||||
private var didPlayAnimation = false
|
||||
|
||||
var cachedDescription: String?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
|
|
@ -91,16 +93,17 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(GradientBackgroundComponent.self)
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
let title = Child(BalancedTextComponent.self)
|
||||
let logo = Child(BundleIconComponent.self)
|
||||
let text = Child(BalancedTextComponent.self)
|
||||
let list = Child(List<Empty>.self)
|
||||
let additionalText = Child(MultilineTextComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
|
||||
let navigateDisposable = MetaDisposable()
|
||||
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
|
|
@ -112,7 +115,6 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
let sideInset: CGFloat = 30.0 + environment.safeInsets.left
|
||||
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
|
||||
|
||||
let titleFont = Font.bold(24.0)
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
|
||||
|
|
@ -122,42 +124,48 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
|
||||
let spacing: CGFloat = 16.0
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 28.0)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: "Premium/Cocoon", tintColor: nil
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + icon.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += icon.size.height
|
||||
contentSize.height += 14.0
|
||||
|
||||
let title = title.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(string: strings.CocoonInfo_Title, font: titleFont, textColor: textColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += spacing - 8.0
|
||||
|
||||
let descriptionText: String
|
||||
if let cachedDescription = state.cachedDescription {
|
||||
descriptionText = cachedDescription
|
||||
} else {
|
||||
func updateText(_ input: String) -> String {
|
||||
let pattern = #"\(([^()]*)\)"#
|
||||
let boldPattern = #"\*\*(.*?)\*\*"#
|
||||
|
||||
let regex = try! NSRegularExpression(pattern: pattern)
|
||||
let boldRegex = try! NSRegularExpression(pattern: boldPattern)
|
||||
|
||||
let nsInput = input as NSString
|
||||
var result = input
|
||||
|
||||
let matches = regex.matches(in: input, range: NSRange(location: 0, length: nsInput.length))
|
||||
|
||||
for match in matches.reversed() {
|
||||
let range = match.range(at: 1)
|
||||
let inner = nsInput.substring(with: range)
|
||||
|
||||
let replacedInner = boldRegex.stringByReplacingMatches(
|
||||
in: inner,
|
||||
range: NSRange(location: 0, length: (inner as NSString).length),
|
||||
withTemplate: "[$1]()"
|
||||
)
|
||||
|
||||
result = (result as NSString).replacingCharacters(in: range, with: replacedInner)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
descriptionText = updateText(strings.CocoonInfo_Description)
|
||||
state.cachedDescription = descriptionText
|
||||
}
|
||||
|
||||
let attributedText = parseMarkdownIntoAttributedString(
|
||||
strings.CocoonInfo_Description,
|
||||
descriptionText,
|
||||
attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: linkColor),
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: UIColor(rgb: 0xb8c9ef)),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: UIColor(rgb: 0xb8c9ef)),
|
||||
link: MarkdownAttributeSet(font: boldTextFont, textColor: UIColor(rgb: 0xffffff)),
|
||||
linkAttribute: { _ in return nil }
|
||||
)
|
||||
)
|
||||
|
|
@ -171,11 +179,52 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let background = background.update(
|
||||
component: GradientBackgroundComponent(
|
||||
colors: [
|
||||
UIColor(rgb: 0x061129),
|
||||
UIColor(rgb: 0x08153d)
|
||||
]
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: text.size.height + 220.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(background
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: "Premium/Cocoon", tintColor: nil
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + icon.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += icon.size.height
|
||||
contentSize.height += 14.0
|
||||
|
||||
let logo = logo.update(
|
||||
component: BundleIconComponent(
|
||||
name: "Premium/CocoonLogo", tintColor: nil
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(logo
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + logo.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += logo.size.height
|
||||
contentSize.height += spacing - 8.0
|
||||
|
||||
context.add(text
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += text.size.height
|
||||
contentSize.height += spacing + 9.0
|
||||
contentSize.height += spacing + 31.0
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
|
|
@ -292,14 +341,14 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: UIColor(rgb: 0x071533),
|
||||
isDark: true,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
tintColor: .white
|
||||
)
|
||||
)),
|
||||
action: { [weak state] _ in
|
||||
|
|
@ -309,7 +358,7 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
|||
state.dismiss(animated: true)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
|
|
@ -667,3 +716,66 @@ private final class ParagraphComponent: CombinedComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class GradientBackgroundComponent: Component {
|
||||
let colors: [UIColor]
|
||||
|
||||
init(
|
||||
colors: [UIColor]
|
||||
) {
|
||||
self.colors = colors
|
||||
}
|
||||
|
||||
static func ==(lhs: GradientBackgroundComponent, rhs: GradientBackgroundComponent) -> Bool {
|
||||
if lhs.colors != rhs.colors {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let gradientLayer: CAGradientLayer
|
||||
|
||||
private var component: GradientBackgroundComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.gradientLayer = CAGradientLayer()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.gradientLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: GradientBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.gradientLayer.frame = CGRect(origin: .zero, size: availableSize)
|
||||
|
||||
var locations: [NSNumber] = []
|
||||
let delta = 1.0 / CGFloat(component.colors.count - 1)
|
||||
for i in 0 ..< component.colors.count {
|
||||
locations.append((delta * CGFloat(i)) as NSNumber)
|
||||
}
|
||||
|
||||
self.gradientLayer.locations = locations
|
||||
self.gradientLayer.colors = component.colors.reversed().map { $0.cgColor }
|
||||
self.gradientLayer.type = .radial
|
||||
self.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
||||
self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
||||
|
||||
self.component = component
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -886,12 +886,12 @@ final class NewContactScreenComponent: Component {
|
|||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
|
||||
let barButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||
let cancelButtonSize = self.cancelButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: barButtonSize,
|
||||
backgroundColor: environment.theme.rootController.navigationBar.opaqueBackgroundColor,
|
||||
backgroundColor: nil,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
|
|
|
|||
|
|
@ -519,13 +519,13 @@ private final class SheetContent: CombinedComponent {
|
|||
contentSize.height += navigation.size.height
|
||||
|
||||
let isBack = items.count > 1
|
||||
let barButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||
let backButton = backButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: barButtonSize,
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
backgroundColor: nil,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: isBack ? "Navigation/Back" : "Navigation/Close",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ struct Particle {
|
|||
|
||||
kernel void dustEffectInitializeParticle(
|
||||
device Particle *particles [[ buffer(0) ]],
|
||||
const device float &verticalDirection [[ buffer(1) ]],
|
||||
uint gid [[ thread_position_in_grid ]]
|
||||
) {
|
||||
Loki rng = Loki(gid);
|
||||
|
|
@ -49,7 +50,9 @@ kernel void dustEffectInitializeParticle(
|
|||
|
||||
float direction = rng.rand() * (3.14159265 * 2.0);
|
||||
float velocity = (0.1 + rng.rand() * (0.2 - 0.1)) * 420.0;
|
||||
particle.velocity = packed_float2(cos(direction) * velocity, sin(direction) * velocity);
|
||||
float2 initialVelocity = float2(cos(direction) * velocity, sin(direction) * velocity);
|
||||
initialVelocity.y *= verticalDirection;
|
||||
particle.velocity = packed_float2(initialVelocity);
|
||||
|
||||
particle.lifetime = 0.7 + rng.rand() * (1.5 - 0.7);
|
||||
|
||||
|
|
@ -107,6 +110,7 @@ kernel void dustEffectUpdateParticle(
|
|||
const device uint2 &size [[ buffer(1) ]],
|
||||
const device float &phase [[ buffer(2) ]],
|
||||
const device float &timeStep [[ buffer(3) ]],
|
||||
const device float &verticalDirection [[ buffer(4) ]],
|
||||
uint gid [[ thread_position_in_grid ]]
|
||||
) {
|
||||
uint count = size.x * size.y;
|
||||
|
|
@ -124,7 +128,7 @@ kernel void dustEffectUpdateParticle(
|
|||
Particle particle = particles[gid];
|
||||
particle.offsetFromBasePosition += (particle.velocity * timeStep) * particleFraction;
|
||||
|
||||
particle.velocity += float2(0.0, timeStep * 120.0) * particleFraction;
|
||||
particle.velocity += float2(0.0, timeStep * 120.0 * verticalDirection) * particleFraction;
|
||||
particle.lifetime = max(0.0, particle.lifetime - timeStep * particleFraction);
|
||||
particles[gid] = particle;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
|||
|
||||
public var animationSpeed: Float = 1.0
|
||||
public var playsBackwards: Bool = false
|
||||
public var animateDown: Bool = false
|
||||
|
||||
public var becameEmpty: (() -> Void)?
|
||||
|
||||
|
|
@ -280,6 +281,8 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
|||
return
|
||||
}
|
||||
|
||||
var verticalDirection: Float = self.animateDown ? -1.0 : 1.0
|
||||
|
||||
for item in self.items {
|
||||
guard let particleBuffer = item.particleBuffer else {
|
||||
continue
|
||||
|
|
@ -297,6 +300,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
|||
if !item.particleBufferIsInitialized {
|
||||
item.particleBufferIsInitialized = true
|
||||
computeEncoder.setComputePipelineState(state.computePipelineStateInitializeParticle)
|
||||
computeEncoder.setBytes(&verticalDirection, length: 4, index: 1)
|
||||
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
||||
}
|
||||
|
||||
|
|
@ -309,6 +313,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
|||
var timeStep: Float = Float(lastTimeStep) / Float(UIView.animationDurationFactor())
|
||||
timeStep *= 2.0
|
||||
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
|
||||
computeEncoder.setBytes(&verticalDirection, length: 4, index: 4)
|
||||
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/Components/ResizableSheetComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue