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?)
|
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 struct ResolvedBotChoosePeerTypes: OptionSet {
|
||||||
public var rawValue: UInt32
|
public var rawValue: UInt32
|
||||||
|
|
||||||
|
|
@ -315,13 +305,12 @@ public enum ResolvedUrl {
|
||||||
case share(url: String?, text: String?, to: String?)
|
case share(url: String?, text: String?, to: String?)
|
||||||
case wallpaper(WallpaperUrlParameter)
|
case wallpaper(WallpaperUrlParameter)
|
||||||
case theme(String)
|
case theme(String)
|
||||||
case settings(ResolvedUrlSettingsSection)
|
|
||||||
case joinVoiceChat(PeerId, String?)
|
case joinVoiceChat(PeerId, String?)
|
||||||
case importStickers
|
case importStickers
|
||||||
case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?)
|
case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?)
|
||||||
case invoice(slug: String, invoice: TelegramMediaInvoice?)
|
case invoice(slug: String, invoice: TelegramMediaInvoice?)
|
||||||
case premiumOffer(reference: String?)
|
case premiumOffer(reference: String?)
|
||||||
case starsTopup(amount: Int64, purpose: String?)
|
case starsTopup(amount: Int64?, purpose: String?)
|
||||||
case chatFolder(slug: String)
|
case chatFolder(slug: String)
|
||||||
case story(peerId: PeerId, id: Int32)
|
case story(peerId: PeerId, id: Int32)
|
||||||
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
|
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
|
||||||
|
|
@ -336,6 +325,53 @@ public enum ResolvedUrl {
|
||||||
case storyFolder(peerId: PeerId, id: Int64)
|
case storyFolder(peerId: PeerId, id: Int64)
|
||||||
case giftCollection(peerId: PeerId, id: Int64)
|
case giftCollection(peerId: PeerId, id: Int64)
|
||||||
case sendGift(peerId: PeerId?)
|
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 {
|
public enum ResolveUrlResult {
|
||||||
|
|
@ -972,18 +1008,30 @@ public protocol MediaEditorScreenResult {
|
||||||
var target: Stories.PendingTarget { get }
|
var target: Stories.PendingTarget { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum StoryCameraMode {
|
||||||
|
case photo
|
||||||
|
case video
|
||||||
|
case live
|
||||||
|
}
|
||||||
|
|
||||||
public protocol TelegramRootControllerInterface: NavigationController {
|
public protocol TelegramRootControllerInterface: NavigationController {
|
||||||
@discardableResult
|
@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 proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
|
||||||
|
|
||||||
func getContactsController() -> ViewController?
|
func getContactsController() -> ViewController?
|
||||||
func getChatsController() -> ViewController?
|
func getChatsController() -> ViewController?
|
||||||
|
func getSettingsController() -> ViewController?
|
||||||
|
|
||||||
func getPrivacySettings() -> Promise<AccountPrivacySettings?>?
|
func getPrivacySettings() -> Promise<AccountPrivacySettings?>?
|
||||||
func openSettings()
|
func getTwoStepAuthData() -> Promise<TwoStepAuthData?>?
|
||||||
|
|
||||||
|
func openContacts()
|
||||||
|
func openSettings(edit: Bool)
|
||||||
func openBirthdaySetup()
|
func openBirthdaySetup()
|
||||||
func openPhotoSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?)
|
func openPhotoSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?)
|
||||||
func openAvatars()
|
func openAvatars()
|
||||||
|
func startNewCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol QuickReplySetupScreenInitialData: AnyObject {
|
public protocol QuickReplySetupScreenInitialData: AnyObject {
|
||||||
|
|
@ -1185,6 +1233,7 @@ public enum ChannelMembersSearchControllerMode {
|
||||||
case promote
|
case promote
|
||||||
case ban
|
case ban
|
||||||
case inviteToCall
|
case inviteToCall
|
||||||
|
case ownershipTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChannelMembersSearchFilter {
|
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 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 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 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 makeGiftDemoScreen(context: AccountContext) -> ViewController
|
||||||
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> 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)?)
|
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 {
|
public protocol PeerInfoScreen: ViewController {
|
||||||
var peerId: PeerId { get }
|
var peerId: PeerId { get }
|
||||||
var privacySettings: Promise<AccountPrivacySettings?> { get }
|
var privacySettings: Promise<AccountPrivacySettings?> { get }
|
||||||
|
var twoStepAuthData: Promise<TwoStepAuthData?> { get }
|
||||||
|
|
||||||
|
func activateEdit()
|
||||||
|
func openEmojiStatusSetup()
|
||||||
func openBirthdaySetup()
|
func openBirthdaySetup()
|
||||||
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
||||||
func togglePaneIsReordering(isReordering: Bool)
|
func togglePaneIsReordering(isReordering: Bool)
|
||||||
func cancelItemSelection()
|
func cancelItemSelection()
|
||||||
func openAvatarSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?)
|
func openAvatarSetup(completedWithUploadingImage: @escaping (UIImage, Signal<PeerInfoAvatarUploadStatus, NoError>) -> UIView?)
|
||||||
func openAvatars()
|
func openAvatars()
|
||||||
|
|
||||||
|
func updateProfilePhoto(_ image: UIImage)
|
||||||
|
func updateProfileVideo(_ image: UIImage, video: Any?, values: Any?, markup: UploadPeerPhotoMarkup?)
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Peer {
|
public extension Peer {
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,7 @@ public protocol ChatListController: ViewController {
|
||||||
func openStoriesFromNotification(peerId: EnginePeer.Id, storyId: Int32)
|
func openStoriesFromNotification(peerId: EnginePeer.Id, storyId: Int32)
|
||||||
|
|
||||||
func resetForumStackIfOpen()
|
func resetForumStackIfOpen()
|
||||||
|
|
||||||
|
func activateEdit()
|
||||||
|
func openEmojiStatusSetup()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||||
self.ignoreUpdatesUntilScrollingStopped = true
|
self.ignoreUpdatesUntilScrollingStopped = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
@available(iOS 13.0, *)
|
||||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
|
||||||
if #available(iOS 14.5, *), navigationAction.shouldPerformDownload {
|
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 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?") {
|
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)
|
decisionHandler(.cancel, preferences)
|
||||||
self.minimize()
|
if !url.contains("domain=oauth") {
|
||||||
|
self.minimize()
|
||||||
|
}
|
||||||
self.openAppUrl(url)
|
self.openAppUrl(url)
|
||||||
} else {
|
} 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)
|
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: {})
|
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {})
|
||||||
} else {
|
} else {
|
||||||
decisionHandler(.allow, preferences)
|
decisionHandler(.allow, preferences)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,20 @@ public enum CallListControllerMode {
|
||||||
case navigation
|
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 final class DeleteAllButtonNode: ASDisplayNode {
|
||||||
private let pressed: () -> Void
|
private let pressed: () -> Void
|
||||||
|
|
||||||
|
|
@ -69,6 +83,8 @@ private final class DeleteAllButtonNode: ASDisplayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public final class CallListController: TelegramBaseController {
|
public final class CallListController: TelegramBaseController {
|
||||||
private var controllerNode: CallListControllerNode {
|
private var controllerNode: CallListControllerNode {
|
||||||
return self.displayNode as! CallListControllerNode
|
return self.displayNode as! CallListControllerNode
|
||||||
|
|
@ -81,6 +97,7 @@ public final class CallListController: TelegramBaseController {
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let mode: CallListControllerMode
|
private let mode: CallListControllerMode
|
||||||
|
private let focusOnItemTag: CallListEntryTag?
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
@ -96,9 +113,15 @@ public final class CallListController: TelegramBaseController {
|
||||||
private let clearDisposable = MetaDisposable()
|
private let clearDisposable = MetaDisposable()
|
||||||
private var createConferenceCallDisposable: Disposable?
|
private var createConferenceCallDisposable: Disposable?
|
||||||
|
|
||||||
public init(context: AccountContext, mode: CallListControllerMode) {
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
|
mode: CallListControllerMode,
|
||||||
|
focusOnItemTag: CallListEntryTag? = nil
|
||||||
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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)
|
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 {
|
if let strongSelf = self {
|
||||||
strongSelf.callPressed()
|
strongSelf.callPressed()
|
||||||
}
|
}
|
||||||
})
|
}, focusOnItemTag: self.focusOnItemTag)
|
||||||
|
|
||||||
if case .navigation = self.mode {
|
if case .navigation = self.mode {
|
||||||
self.controllerNode.navigationBar = self.navigationBar
|
self.controllerNode.navigationBar = self.navigationBar
|
||||||
|
|
@ -421,6 +444,18 @@ public final class CallListController: TelegramBaseController {
|
||||||
}
|
}
|
||||||
self._ready.set(self.controllerNode.ready)
|
self._ready.set(self.controllerNode.ready)
|
||||||
self.displayNodeDidLoad()
|
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 {
|
override public var navigationEdgeEffectExtension: CGFloat {
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
||||||
case let .displayTab(_, text, value):
|
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
|
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)
|
nodeInteraction.updateShowCallsTab(value)
|
||||||
}), directionHint: entry.directionHint)
|
}, tag: CallListEntryTag.showTab), directionHint: entry.directionHint)
|
||||||
case let .displayTabInfo(_, text):
|
case let .displayTabInfo(_, text):
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
||||||
case .openNewCall:
|
case .openNewCall:
|
||||||
|
|
@ -149,7 +149,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
||||||
case let .displayTab(_, text, value):
|
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
|
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)
|
nodeInteraction.updateShowCallsTab(value)
|
||||||
}), directionHint: entry.directionHint)
|
}, tag: CallListEntryTag.showTab), directionHint: entry.directionHint)
|
||||||
case let .displayTabInfo(_, text):
|
case let .displayTabInfo(_, text):
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
||||||
case .openNewCall:
|
case .openNewCall:
|
||||||
|
|
@ -184,6 +184,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let mode: CallListControllerMode
|
private let mode: CallListControllerMode
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
private var focusOnItemTag: CallListEntryTag?
|
||||||
|
|
||||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
|
@ -241,7 +242,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||||
|
|
||||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
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.controller = controller
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
|
@ -253,6 +254,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||||
self.openNewCall = openNewCall
|
self.openNewCall = openNewCall
|
||||||
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
||||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||||
|
|
@ -861,6 +863,16 @@ final class CallListControllerNode: ASDisplayNode {
|
||||||
strongSelf._ready.set(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,10 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent",
|
"//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent",
|
||||||
"//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode",
|
"//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode",
|
||||||
"//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent",
|
"//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 = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||||
private var didSuggestLoginEmailSetup = false
|
private var didSuggestLoginEmailSetup = false
|
||||||
private var didSuggestLoginPasskeySetup = false
|
private var didSuggestLoginPasskeySetup = false
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private(set) var presentationData: PresentationData
|
||||||
private let presentationDataValue = Promise<PresentationData>()
|
private let presentationDataValue = Promise<PresentationData>()
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
|
@ -3029,7 +3029,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||||
}
|
}
|
||||||
|
|
||||||
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
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()
|
coordinator?.animateIn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3511,7 +3511,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||||
super.navigationStackConfigurationUpdated(next: next)
|
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 self.secondaryContext == nil {
|
||||||
if case .chatList(.root) = self.chatListDisplayNode.effectiveContainerNode.location {
|
if case .chatList(.root) = self.chatListDisplayNode.effectiveContainerNode.location {
|
||||||
self.effectiveContext?.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
|
self.effectiveContext?.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
|
||||||
|
|
@ -5874,10 +5884,33 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||||
})
|
})
|
||||||
], parseMarkdown: true), in: .window(.root))
|
], parseMarkdown: true), in: .window(.root))
|
||||||
} else {
|
} else {
|
||||||
completion(true)
|
let proceed = {
|
||||||
self.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
|
completion(true)
|
||||||
removed()
|
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 {
|
if let current = self.storyCameraTransitionInCoordinator {
|
||||||
coordinator = current
|
coordinator = current
|
||||||
} else {
|
} 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 {
|
guard let self, let target else {
|
||||||
return nil
|
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
|
let initialName: ChatFolderTitle
|
||||||
if let initialPreset {
|
if let initialPreset {
|
||||||
initialName = initialPreset.title
|
initialName = initialPreset.title
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ private enum ChatListFilterPresetListSection: Int32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChatListFilterPresetListEntryTag: ItemListItemTag {
|
public enum ChatListFilterPresetListEntryTag: ItemListItemTag {
|
||||||
|
case edit
|
||||||
|
case addRecommended
|
||||||
case displayTags
|
case displayTags
|
||||||
|
|
||||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
|
|
@ -175,7 +177,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||||
case let .screenHeader(text):
|
case let .screenHeader(text):
|
||||||
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animation: .folders, sectionId: self.section)
|
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animation: .folders, sectionId: self.section)
|
||||||
case let .suggestedListHeader(text):
|
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):
|
case let .suggestedPreset(_, title, label, preset):
|
||||||
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, systemStyle: .glass, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: {
|
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, systemStyle: .glass, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: {
|
||||||
arguments.addSuggestedPressed(title, preset)
|
arguments.addSuggestedPressed(title, preset)
|
||||||
|
|
@ -318,7 +320,7 @@ public enum ChatListFilterPresetListControllerMode {
|
||||||
case modal
|
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 initialState = ChatListFilterPresetListControllerState()
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
|
|
@ -326,6 +328,14 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if focusOnItemTag == .edit {
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.isEditing = true
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var presentControllerImpl: ((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 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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -797,6 +807,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var didFocusOnItem = false
|
||||||
controller.afterTransactionCompleted = { [weak controller] in
|
controller.afterTransactionCompleted = { [weak controller] in
|
||||||
guard let toggleDirection = animateNextShowHideTagsTransition.swap(nil) else {
|
guard let toggleDirection = animateNextShowHideTagsTransition.swap(nil) else {
|
||||||
return
|
return
|
||||||
|
|
@ -819,6 +831,15 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||||
}
|
}
|
||||||
delay += 0.02
|
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
|
return controller
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ public final class FilledRoundedRectangleComponent: Component {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if component.smoothCorners {
|
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 {
|
if let cornerImage = self.cornerImage, cornerImage.size == size {
|
||||||
} else {
|
} else {
|
||||||
self.cornerImage = generateImage(size, rotatedContext: { size, context in
|
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)
|
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 {
|
if #available(iOS 18.0, *), let rootViewController = context.sharedContext.mainWindow?.viewController?.view.window?.rootViewController {
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let pickerView = ContactAccessPickerHostingView(completionHandler: { [weak rootViewController] ids in
|
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 {
|
public extension UIView {
|
||||||
func snapshotContentTree(unhide: Bool = false, keepPortals: Bool = false, keepTransform: Bool = false) -> UIView? {
|
func snapshotContentTree(unhide: Bool = false, keepPortals: Bool = false, keepTransform: Bool = false) -> UIView? {
|
||||||
let wasHidden = self.isHidden
|
let wasHidden = self.isHidden
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,9 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||||
let noInsets: Bool
|
let noInsets: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
public let action: (() -> Void)?
|
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.presentationData = presentationData
|
||||||
self.style = style
|
self.style = style
|
||||||
self.systemStyle = systemStyle
|
self.systemStyle = systemStyle
|
||||||
|
|
@ -52,6 +53,7 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||||
self.color = color
|
self.color = color
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
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) {
|
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()
|
private let iconDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
public var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag as? ItemListItemTag
|
||||||
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ public extension ItemListItem {
|
||||||
|
|
||||||
public protocol ItemListItemNode {
|
public protocol ItemListItemNode {
|
||||||
var tag: ItemListItemTag? { get }
|
var tag: ItemListItemTag? { get }
|
||||||
|
|
||||||
|
func displayHighlight()
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ItemListItemNode {
|
||||||
|
func displayHighlight() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol ItemListItemFocusableNode {
|
public protocol ItemListItemFocusableNode {
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,9 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let deleteAction: (() -> 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.presentationData = presentationData
|
||||||
self.systemStyle = systemStyle
|
self.systemStyle = systemStyle
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
|
@ -60,6 +61,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
self.action = action
|
||||||
self.deleteAction = deleteAction
|
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) {
|
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
|
return self.contentParentNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag as? ItemListItemTag
|
||||||
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ private let boldBadgeFont = Font.semibold(14.0)
|
||||||
|
|
||||||
public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let highlightNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
@ -215,6 +216,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
self.backgroundNode.backgroundColor = .white
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
|
self.highlightNode = ASDisplayNode()
|
||||||
|
self.highlightNode.isLayerBacked = true
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
self.maskNode = ASImageNode()
|
||||||
self.maskNode.isUserInteractionEnabled = false
|
self.maskNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
|
@ -283,6 +287,20 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
transition.updateAlpha(node: self.arrowNode, alpha: hasContextMenu ? 0.5 : 1.0)
|
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) {
|
public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode.textNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode.textNode)
|
||||||
let makeTitleWithEntitiesLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
|
let makeTitleWithEntitiesLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
|
||||||
|
|
@ -600,6 +618,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
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 {
|
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.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.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.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.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))
|
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 actionText: String?
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
|
public let tag: ItemListItemTag?
|
||||||
|
|
||||||
public let isAlwaysPlain: Bool = true
|
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.presentationData = presentationData
|
||||||
self.text = text
|
self.text = text
|
||||||
self.badge = badge
|
self.badge = badge
|
||||||
|
|
@ -73,6 +74,7 @@ public class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
||||||
self.actionText = actionText
|
self.actionText = actionText
|
||||||
self.action = action
|
self.action = action
|
||||||
self.sectionId = sectionId
|
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) {
|
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
|
private let activateArea: AccessibilityAreaNode
|
||||||
|
|
||||||
|
public var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag
|
||||||
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ extension IconSwitchNode: ItemListSwitchNodeImpl {
|
||||||
|
|
||||||
public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let highlightNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
@ -175,6 +176,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
self.backgroundNode.backgroundColor = .white
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
|
self.highlightNode = ASDisplayNode()
|
||||||
|
self.highlightNode.isLayerBacked = true
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
self.maskNode = ASImageNode()
|
||||||
self.maskNode.isUserInteractionEnabled = false
|
self.maskNode.isUserInteractionEnabled = false
|
||||||
|
|
@ -234,6 +238,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
self.switchGestureNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
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) {
|
func asyncLayout() -> (_ item: ItemListSwitchItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
|
@ -400,14 +418,13 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||||
|
|
||||||
strongSelf.switchNode.frameColor = item.presentationData.theme.list.itemSwitchColors.frameColor
|
strongSelf.switchNode.frameColor = item.presentationData.theme.list.itemSwitchColors.frameColor
|
||||||
strongSelf.switchNode.contentColor = item.presentationData.theme.list.itemSwitchColors.contentColor
|
strongSelf.switchNode.contentColor = item.presentationData.theme.list.itemSwitchColors.contentColor
|
||||||
strongSelf.switchNode.handleColor = item.presentationData.theme.list.itemSwitchColors.handleColor
|
strongSelf.switchNode.handleColor = item.presentationData.theme.list.itemSwitchColors.handleColor
|
||||||
strongSelf.switchNode.positiveContentColor = item.presentationData.theme.list.itemSwitchColors.positiveColor
|
strongSelf.switchNode.positiveContentColor = item.presentationData.theme.list.itemSwitchColors.positiveColor
|
||||||
strongSelf.switchNode.negativeContentColor = item.presentationData.theme.list.itemSwitchColors.negativeColor
|
strongSelf.switchNode.negativeContentColor = item.presentationData.theme.list.itemSwitchColors.negativeColor
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||||
|
strongSelf.highlightNode.backgroundColor = item.presentationData.theme.list.itemSearchHighlightColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = titleApply()
|
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
|
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.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.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.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)))
|
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
|
#pragma mark Initialization and teardown
|
||||||
|
|
||||||
- (GPUImageRotationMode)rotationForTrack:(AVAsset *)asset {
|
- (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];
|
CGAffineTransform t = [videoTrack preferredTransform];
|
||||||
|
|
||||||
if (t.a == -1 && t.d == -1) {
|
if (t.a == -1 && t.d == -1) {
|
||||||
|
|
|
||||||
|
|
@ -428,9 +428,9 @@
|
||||||
if (itemChanged) {
|
if (itemChanged) {
|
||||||
[self _playerCleanup];
|
[self _playerCleanup];
|
||||||
|
|
||||||
if (!item.asFile) {
|
// if (!item.asFile) {
|
||||||
[_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startStrictWithNext:nil file:__FILE_NAME__ line:__LINE__]];
|
// [_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startStrictWithNext:nil file:__FILE_NAME__ line:__LINE__]];
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
_scrubberView.allowsTrimming = false;
|
_scrubberView.allowsTrimming = false;
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/CounterControllerTitleView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import SearchUI
|
import SearchUI
|
||||||
|
import CounterControllerTitleView
|
||||||
|
|
||||||
public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMembersSearchController {
|
public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMembersSearchController {
|
||||||
private let queue = Queue()
|
private let queue = Queue()
|
||||||
|
|
@ -49,7 +50,18 @@ public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMe
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
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))
|
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.presentationData = presentationData
|
||||||
strongSelf.controllerNode.updatePresentationData(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)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
if let strongSelf = self {
|
guard let self, let peer else {
|
||||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
return
|
||||||
strongSelf.title = strongSelf.presentationData.strings.Channel_Subscribers_Title
|
}
|
||||||
|
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 {
|
} 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
|
let renderedParticipant: RenderedChannelParticipant
|
||||||
switch participant {
|
switch participant {
|
||||||
|
|
@ -531,7 +538,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||||
var label: String?
|
var label: String?
|
||||||
var enabled = true
|
var enabled = true
|
||||||
switch mode {
|
switch mode {
|
||||||
case .ban, .promote:
|
case .ban, .promote, .ownershipTransfer:
|
||||||
if participant.peer.id == context.account.peerId {
|
if participant.peer.id == context.account.peerId {
|
||||||
continue participantsLoop
|
continue participantsLoop
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,10 +158,10 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||||
|
|
||||||
let closeButton = closeButton.update(
|
let closeButton = closeButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: theme.overallDarkAppearance,
|
isDark: theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
|
|
@ -172,7 +172,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||||
component.cancel(true)
|
component.cancel(true)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -496,11 +496,11 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(title
|
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
|
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
|
var originY: CGFloat = 0.0
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
|
@ -38,6 +39,11 @@ import PremiumStarComponent
|
||||||
import PremiumCoinComponent
|
import PremiumCoinComponent
|
||||||
import EdgeEffect
|
import EdgeEffect
|
||||||
|
|
||||||
|
public enum PremiumIntroEntryTag {
|
||||||
|
case doNotHideAds
|
||||||
|
}
|
||||||
|
private let doNotHideAdsTag = GenericComponentViewTag()
|
||||||
|
|
||||||
public enum PremiumSource: Equatable {
|
public enum PremiumSource: Equatable {
|
||||||
public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
|
public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
|
|
@ -2677,7 +2683,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||||
let _ = accountContext.engine.accountData.updateAdMessagesEnabled(enabled: value).startStandalone()
|
let _ = accountContext.engine.accountData.updateAdMessagesEnabled(enabled: value).startStandalone()
|
||||||
state?.updated(transition: .immediate)
|
state?.updated(transition: .immediate)
|
||||||
})),
|
})),
|
||||||
action: nil
|
action: nil,
|
||||||
|
tag: doNotHideAdsTag
|
||||||
))))
|
))))
|
||||||
|
|
||||||
let adsInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Business_AdsInfo, attributes: termsMarkdownAttributes, textAlignment: .natural
|
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
|
private let screenContext: ScreenContext
|
||||||
fileprivate let mode: Mode
|
fileprivate let mode: Mode
|
||||||
|
private let focusOnItemTag: PremiumIntroEntryTag?
|
||||||
|
|
||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
private let _ready = Promise<Bool>()
|
private let _ready = Promise<Bool>()
|
||||||
|
|
@ -3932,13 +3940,14 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||||
|
|
||||||
private let overNavigationContainer: UIView
|
private let overNavigationContainer: UIView
|
||||||
|
|
||||||
public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) {
|
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)
|
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.screenContext = screenContext
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
let presentationData = screenContext.presentationData
|
let presentationData = screenContext.presentationData
|
||||||
|
|
||||||
|
|
@ -4066,6 +4075,20 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||||
|
|
||||||
override public func viewDidLoad() {
|
override public func viewDidLoad() {
|
||||||
super.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) {
|
public override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
|
|
||||||
|
|
@ -857,10 +857,10 @@ private final class LimitSheetContent: CombinedComponent {
|
||||||
|
|
||||||
let closeButton = closeButton.update(
|
let closeButton = closeButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: theme.overallDarkAppearance,
|
isDark: theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
|
|
@ -872,7 +872,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||||
component.cancel()
|
component.cancel()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(closeButton
|
context.add(closeButton
|
||||||
|
|
@ -1519,7 +1519,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
context.add(title
|
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
|
var textSize: CGSize
|
||||||
|
|
|
||||||
|
|
@ -163,10 +163,10 @@ private final class SheetContent: CombinedComponent {
|
||||||
|
|
||||||
let closeButton = closeButton.update(
|
let closeButton = closeButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: theme.overallDarkAppearance,
|
isDark: theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
|
|
@ -177,7 +177,7 @@ private final class SheetContent: CombinedComponent {
|
||||||
component.dismiss()
|
component.dismiss()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(closeButton
|
context.add(closeButton
|
||||||
|
|
|
||||||
|
|
@ -159,25 +159,25 @@ private final class SheetContent: CombinedComponent {
|
||||||
textString = ""
|
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(
|
let closeButton = closeButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: theme.overallDarkAppearance,
|
isDark: theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
|
tintColor: theme.chat.inputPanel.panelControlColor
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
action: { _ in
|
action: { _ in
|
||||||
component.dismiss()
|
component.dismiss()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(closeButton
|
context.add(closeButton
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,10 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/AlertComponent",
|
"//submodules/TelegramUI/Components/AlertComponent",
|
||||||
"//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
|
"//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
|
||||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||||
|
"//submodules/TelegramUI/Components/AvatarEditorScreen",
|
||||||
|
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
||||||
|
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,22 @@ func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl
|
||||||
for item in items {
|
for item in items {
|
||||||
if case let .text(itemText, _) = item, case let .url(text, url, _) = itemText {
|
if case let .text(itemText, _) = item, case let .url(text, url, _) = itemText {
|
||||||
let (_, anchor) = extractAnchor(string: url)
|
let (_, anchor) = extractAnchor(string: url)
|
||||||
|
guard let anchor else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
var index = nextIndex
|
var index = nextIndex
|
||||||
if suggestAccountDeletion && (anchor?.contains("delete-my-account") ?? false) {
|
if suggestAccountDeletion && anchor.contains("delete-my-account") {
|
||||||
index = 1
|
index = 1
|
||||||
} else {
|
} else {
|
||||||
nextIndex += 1
|
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))
|
let controller = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel))
|
||||||
present(.push, controller)
|
present(.push, controller)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,23 @@ private enum AutodownloadMediaCategorySection: Int32 {
|
||||||
case types
|
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 {
|
private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||||
case master(PresentationTheme, String, Bool)
|
case master(PresentationTheme, String, Bool)
|
||||||
case dataUsageHeader(PresentationTheme, String)
|
case dataUsageHeader(PresentationTheme, String)
|
||||||
|
|
@ -155,31 +172,31 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||||
case let .master(_, text, value):
|
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
|
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)
|
arguments.toggleMaster(value)
|
||||||
})
|
}, tag: AutodownloadMediaCategoryEntryTag.master)
|
||||||
case let .dataUsageHeader(_, text):
|
case let .dataUsageHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .dataUsageItem(theme, strings, value, customPosition, enabled):
|
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
|
return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, systemStyle: .glass, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in
|
||||||
arguments.changePreset(preset)
|
arguments.changePreset(preset)
|
||||||
})
|
}, tag: AutodownloadMediaCategoryEntryTag.usage)
|
||||||
case let .typesHeader(_, text):
|
case let .typesHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .photos(_, text, value, enabled):
|
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: {
|
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)
|
arguments.customize(.photo)
|
||||||
})
|
}, tag: AutodownloadMediaCategoryEntryTag.photos)
|
||||||
case let .stories(_, text, value, enabled):
|
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: {
|
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)
|
arguments.customize(.story)
|
||||||
})
|
}, tag: AutodownloadMediaCategoryEntryTag.stories)
|
||||||
case let .videos(_, text, value, enabled):
|
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: {
|
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)
|
arguments.customize(.video)
|
||||||
})
|
}, tag: AutodownloadMediaCategoryEntryTag.videos)
|
||||||
case let .files(_, text, value, enabled):
|
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: {
|
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)
|
arguments.customize(.file)
|
||||||
})
|
}, tag: AutodownloadMediaCategoryEntryTag.files)
|
||||||
case let .voiceMessagesInfo(_, text):
|
case let .voiceMessagesInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +317,7 @@ private func autodownloadMediaConnectionTypeControllerEntries(presentationData:
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func autodownloadMediaConnectionTypeController(context: AccountContext, connectionType: AutomaticDownloadConnectionType) -> ViewController {
|
func autodownloadMediaConnectionTypeController(context: AccountContext, connectionType: AutomaticDownloadConnectionType, focusOnItemTag: AutodownloadMediaCategoryEntryTag? = nil) -> ViewController {
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
||||||
let arguments = AutodownloadMediaConnectionTypeControllerArguments(toggleMaster: { value in
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -379,5 +396,20 @@ func autodownloadMediaConnectionTypeController(context: AccountContext, connecti
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(c)
|
(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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,9 @@ final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let updated: (AutomaticDownloadDataUsage) -> Void
|
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.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.systemStyle = systemStyle
|
self.systemStyle = systemStyle
|
||||||
|
|
@ -50,6 +51,7 @@ final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.updated = updated
|
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) {
|
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 backgroundNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
@ -103,6 +105,10 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
|
||||||
private var item: AutodownloadDataUsagePickerItem?
|
private var item: AutodownloadDataUsagePickerItem?
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
private var layoutParams: ListViewItemLayoutParams?
|
||||||
|
|
||||||
|
public var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,9 @@ final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
||||||
let range: Range<Int64>?
|
let range: Range<Int64>?
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let updated: (Int64) -> Void
|
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.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.systemStyle = systemStyle
|
self.systemStyle = systemStyle
|
||||||
|
|
@ -69,6 +70,7 @@ final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
||||||
self.range = range
|
self.range = range
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.updated = updated
|
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) {
|
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 backgroundNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
@ -119,6 +121,10 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
||||||
private var item: AutodownloadSizeLimitItem?
|
private var item: AutodownloadSizeLimitItem?
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
private var layoutParams: ListViewItemLayoutParams?
|
||||||
|
|
||||||
|
public var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ public enum DataAndStorageEntryTag: ItemListItemTag, Equatable {
|
||||||
case raiseToListen
|
case raiseToListen
|
||||||
case autoSave(AutomaticSaveIncomingPeerType)
|
case autoSave(AutomaticSaveIncomingPeerType)
|
||||||
case sensitiveContent
|
case sensitiveContent
|
||||||
|
case useLessVoiceData
|
||||||
|
|
||||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
if let other = other as? DataAndStorageEntryTag, self == other {
|
if let other = other as? DataAndStorageEntryTag, self == other {
|
||||||
|
|
@ -409,7 +410,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||||
case let .useLessVoiceData(_, text, value):
|
case let .useLessVoiceData(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.toggleVoiceUseLessData(value)
|
arguments.toggleVoiceUseLessData(value)
|
||||||
}, tag: nil)
|
}, tag: DataAndStorageEntryTag.useLessVoiceData)
|
||||||
case let .useLessVoiceDataInfo(_, text):
|
case let .useLessVoiceDataInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .otherHeader(_, text):
|
case let .otherHeader(_, text):
|
||||||
|
|
@ -1012,6 +1013,20 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
||||||
update()
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import PresentationDataUtils
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
||||||
enum ItemType: CaseIterable {
|
public enum EnergySavingItemType: CaseIterable {
|
||||||
case autoplayVideo
|
case autoplayVideo
|
||||||
case autoplayGif
|
case autoplayGif
|
||||||
case loopStickers
|
case loopStickers
|
||||||
|
|
@ -88,10 +88,10 @@ enum ItemType: CaseIterable {
|
||||||
|
|
||||||
private final class EnergeSavingSettingsScreenArguments {
|
private final class EnergeSavingSettingsScreenArguments {
|
||||||
let updateThreshold: (Int32) -> Void
|
let updateThreshold: (Int32) -> Void
|
||||||
let toggleItem: (ItemType) -> Void
|
let toggleItem: (EnergySavingItemType) -> Void
|
||||||
let displayDisabledTooltip: () -> 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.updateThreshold = updateThreshold
|
||||||
self.toggleItem = toggleItem
|
self.toggleItem = toggleItem
|
||||||
self.displayDisabledTooltip = displayDisabledTooltip
|
self.displayDisabledTooltip = displayDisabledTooltip
|
||||||
|
|
@ -103,19 +103,31 @@ private enum EnergeSavingSettingsScreenSection: Int32 {
|
||||||
case items
|
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 {
|
private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry {
|
||||||
enum StableId: Hashable {
|
enum StableId: Hashable {
|
||||||
case allHeader
|
case allHeader
|
||||||
case all
|
case all
|
||||||
case allFooter
|
case allFooter
|
||||||
case itemsHeader
|
case itemsHeader
|
||||||
case item(ItemType)
|
case item(EnergySavingItemType)
|
||||||
}
|
}
|
||||||
|
|
||||||
case allHeader(Bool?)
|
case allHeader(Bool?)
|
||||||
case all(Int32)
|
case all(Int32)
|
||||||
case allFooter(String)
|
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
|
case itemsHeader
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
|
|
@ -197,7 +209,7 @@ private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry {
|
||||||
arguments.toggleItem(type)
|
arguments.toggleItem(type)
|
||||||
}, activatedWhileDisabled: {
|
}, activatedWhileDisabled: {
|
||||||
arguments.displayDisabledTooltip()
|
arguments.displayDisabledTooltip()
|
||||||
})
|
}, tag: EnergySavingEntryTag.item(type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -241,14 +253,14 @@ private func energeSavingSettingsScreenEntries(
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.itemsHeader)
|
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))
|
entries.append(.item(index: entries.count, type: type, value: settings.energyUsageSettings[keyPath: type.settingsKeyPath] && itemsEnabled, enabled: itemsEnabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func energySavingSettingsScreen(context: AccountContext) -> ViewController {
|
public func energySavingSettingsScreen(context: AccountContext, focusOnItemTag: EnergySavingEntryTag? = nil) -> ViewController {
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
let _ = pushControllerImpl
|
let _ = pushControllerImpl
|
||||||
|
|
||||||
|
|
@ -300,7 +312,7 @@ public func energySavingSettingsScreen(context: AccountContext) -> ViewControlle
|
||||||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||||
animateChanges: false
|
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))
|
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)
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,20 @@ import AccountContext
|
||||||
import TelegramIntents
|
import TelegramIntents
|
||||||
import AccountUtils
|
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 {
|
private final class IntentsSettingsControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let updateSettings: (@escaping (IntentsSettings) -> IntentsSettings) -> Void
|
let updateSettings: (@escaping (IntentsSettings) -> IntentsSettings) -> Void
|
||||||
|
|
@ -197,7 +211,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
||||||
case let .contacts(_, text, value):
|
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
|
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) }
|
arguments.updateSettings { $0.withUpdatedContacts(value) }
|
||||||
})
|
}, tag: IntentsEntryTag.suggested)
|
||||||
case let .savedMessages(_, text, value):
|
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
|
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) }
|
arguments.updateSettings { $0.withUpdatedSavedMessages(value) }
|
||||||
|
|
@ -217,7 +231,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
||||||
case let .suggestAll(_, text, value):
|
case let .suggestAll(_, text, value):
|
||||||
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||||
arguments.updateSettings { $0.withUpdatedOnlyShared(false) }
|
arguments.updateSettings { $0.withUpdatedOnlyShared(false) }
|
||||||
})
|
}, tag: IntentsEntryTag.suggestBy)
|
||||||
case let .suggestOnlyShared(_, text, value):
|
case let .suggestOnlyShared(_, text, value):
|
||||||
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||||
arguments.updateSettings { $0.withUpdatedOnlyShared(true) }
|
arguments.updateSettings { $0.withUpdatedOnlyShared(true) }
|
||||||
|
|
@ -226,7 +240,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
|
||||||
case let .resetAll(_, text):
|
case let .resetAll(_, text):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.resetAll()
|
arguments.resetAll()
|
||||||
})
|
}, tag: IntentsEntryTag.reset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -261,7 +275,7 @@ private func intentsSettingsControllerEntries(context: AccountContext, presentat
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func intentsSettingsController(context: AccountContext) -> ViewController {
|
public func intentsSettingsController(context: AccountContext, focusOnItemTag: IntentsEntryTag? = nil) -> ViewController {
|
||||||
var presentControllerImpl: ((ViewController) -> Void)?
|
var presentControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
||||||
let arguments = IntentsSettingsControllerArguments(context: context, updateSettings: { f in
|
let arguments = IntentsSettingsControllerArguments(context: context, updateSettings: { f in
|
||||||
|
|
@ -317,5 +331,20 @@ public func intentsSettingsController(context: AccountContext) -> ViewController
|
||||||
presentControllerImpl = { [weak controller] c in
|
presentControllerImpl = { [weak controller] c in
|
||||||
controller?.present(c, in: .window(.root))
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,21 @@ private enum ProxySettingsControllerEntryId: Equatable, Hashable {
|
||||||
case server(String, Int32, ProxyServerConnection)
|
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 {
|
private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
||||||
case enabled(PresentationTheme, String, Bool, Bool)
|
case enabled(PresentationTheme, String, Bool, Bool)
|
||||||
case serversHeader(PresentationTheme, String)
|
case serversHeader(PresentationTheme, String)
|
||||||
|
|
@ -207,7 +222,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
||||||
} else {
|
} else {
|
||||||
arguments.toggleEnabled(value)
|
arguments.toggleEnabled(value)
|
||||||
}
|
}
|
||||||
})
|
}, tag: ProxySettingsEntryTag.useProxy)
|
||||||
case let .serversHeader(_, text):
|
case let .serversHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .addServer(_, text, _):
|
case let .addServer(_, text, _):
|
||||||
|
|
@ -227,11 +242,11 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
||||||
case let .shareProxyList(_, text):
|
case let .shareProxyList(_, text):
|
||||||
return ProxySettingsActionItem(presentationData: presentationData, systemStyle: .glass, title: text, sectionId: self.section, editing: false, action: {
|
return ProxySettingsActionItem(presentationData: presentationData, systemStyle: .glass, title: text, sectionId: self.section, editing: false, action: {
|
||||||
arguments.shareProxyList()
|
arguments.shareProxyList()
|
||||||
})
|
}, tag: ProxySettingsEntryTag.shareList)
|
||||||
case let .useForCalls(_, text, value):
|
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
|
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)
|
arguments.toggleUseForCalls(value)
|
||||||
})
|
}, tag: ProxySettingsEntryTag.useForCalls)
|
||||||
case let .useForCallsInfo(_, text):
|
case let .useForCallsInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
}
|
}
|
||||||
|
|
@ -308,12 +323,12 @@ public enum ProxySettingsControllerMode {
|
||||||
case modal
|
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 }
|
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 pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let stateValue = Atomic(value: ProxySettingsControllerState())
|
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)?
|
var shareProxyListImpl: (() -> Void)?
|
||||||
|
|
||||||
let arguments = ProxySettingsControllerArguments(toggleEnabled: { value in
|
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 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))
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ final class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
||||||
let editing: Bool
|
let editing: Bool
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let action: () -> Void
|
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.presentationData = presentationData
|
||||||
self.systemStyle = systemStyle
|
self.systemStyle = systemStyle
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
@ -29,6 +30,7 @@ final class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
||||||
self.editing = editing
|
self.editing = editing
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
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) {
|
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 backgroundNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
@ -89,6 +91,10 @@ private final class ProxySettingsActionItemNode: ListViewItemNode {
|
||||||
|
|
||||||
private var item: ProxySettingsActionItem?
|
private var item: ProxySettingsActionItem?
|
||||||
|
|
||||||
|
public var tag: ItemListItemTag? {
|
||||||
|
return self.item?.tag
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,20 @@ enum SaveIncomingMediaSection: ItemListSectionId {
|
||||||
case deleteAllExceptions
|
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 {
|
private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
||||||
enum StableId: Hashable {
|
enum StableId: Hashable {
|
||||||
case peer
|
case peer
|
||||||
|
|
@ -200,7 +214,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
||||||
case let .videoSize(decimalSeparator, text, size):
|
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
|
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)
|
arguments.updateMaximumVideoSize(value)
|
||||||
})
|
}, tag: SaveIncomingMediaEntryTag.maxVideoSize)
|
||||||
case let .videoInfo(text):
|
case let .videoInfo(text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||||
case let .exceptionsHeader(title):
|
case let .exceptionsHeader(title):
|
||||||
|
|
@ -209,7 +223,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
||||||
let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme)
|
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: {
|
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: icon, title: title, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: {
|
||||||
arguments.openAddException()
|
arguments.openAddException()
|
||||||
})
|
}, tag: SaveIncomingMediaEntryTag.addException)
|
||||||
case let .exceptionItem(_, peer, label):
|
case let .exceptionItem(_, peer, label):
|
||||||
return ItemListPeerItem(
|
return ItemListPeerItem(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
|
|
@ -248,7 +262,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
|
||||||
case let .deleteAllExceptions(title):
|
case let .deleteAllExceptions(title):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.deleteAllExceptions()
|
arguments.deleteAllExceptions()
|
||||||
})
|
}, tag: SaveIncomingMediaEntryTag.deleteExceptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +366,7 @@ private func saveIncomingMediaControllerEntries(presentationData: PresentationDa
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SaveIncomingMediaScope {
|
public enum SaveIncomingMediaScope {
|
||||||
case peer(EnginePeer.Id)
|
case peer(EnginePeer.Id)
|
||||||
case addPeer(id: EnginePeer.Id, completion: (MediaAutoSaveConfiguration) -> Void)
|
case addPeer(id: EnginePeer.Id, completion: (MediaAutoSaveConfiguration) -> Void)
|
||||||
case peerType(AutomaticSaveIncomingPeerType)
|
case peerType(AutomaticSaveIncomingPeerType)
|
||||||
|
|
@ -363,7 +377,7 @@ private struct SaveIncomingMediaControllerState: Equatable {
|
||||||
var peerIdWithOptions: EnginePeer.Id?
|
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 stateValue = Atomic(value: SaveIncomingMediaControllerState())
|
||||||
let statePromise = ValuePromise<SaveIncomingMediaControllerState>(stateValue.with { $0 })
|
let statePromise = ValuePromise<SaveIncomingMediaControllerState>(stateValue.with { $0 })
|
||||||
let updateState: ((SaveIncomingMediaControllerState) -> SaveIncomingMediaControllerState) -> Void = { f in
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -743,6 +757,20 @@ func saveIncomingMediaController(context: AccountContext, scope: SaveIncomingMed
|
||||||
controller?.dismiss()
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,24 @@ import TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import SearchUI
|
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 {
|
public class LocalizationListController: ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let focusOnItemTag: LocalizationListEntryTag?
|
||||||
|
|
||||||
private var controllerNode: LocalizationListControllerNode {
|
private var controllerNode: LocalizationListControllerNode {
|
||||||
return self.displayNode as! LocalizationListControllerNode
|
return self.displayNode as! LocalizationListControllerNode
|
||||||
|
|
@ -31,8 +46,9 @@ public class LocalizationListController: ViewController {
|
||||||
|
|
||||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||||
|
|
||||||
public init(context: AccountContext) {
|
public init(context: AccountContext, focusOnItemTag: LocalizationListEntryTag? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
|
@ -125,7 +141,7 @@ public class LocalizationListController: ViewController {
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}, push: { [weak self] c in
|
}, push: { [weak self] c in
|
||||||
self?.push(c)
|
self?.push(c)
|
||||||
})
|
}, focusOnItemTag: self.focusOnItemTag)
|
||||||
|
|
||||||
self.controllerNode.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
self.controllerNode.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ private enum LanguageListEntry: Comparable, Identifiable {
|
||||||
case let .translate(text, value):
|
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
|
return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, value: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in
|
||||||
toggleShowTranslate(value)
|
toggleShowTranslate(value)
|
||||||
})
|
}, tag: LocalizationListEntryTag.showButton)
|
||||||
case let .translateEntire(text, value, locked):
|
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
|
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 {
|
if !locked {
|
||||||
|
|
@ -103,7 +103,7 @@ private enum LanguageListEntry: Comparable, Identifiable {
|
||||||
}
|
}
|
||||||
}, activatedWhileDisabled: {
|
}, activatedWhileDisabled: {
|
||||||
showPremiumInfo()
|
showPremiumInfo()
|
||||||
})
|
}, tag: LocalizationListEntryTag.translateChats)
|
||||||
case let .doNotTranslate(text, value):
|
case let .doNotTranslate(text, value):
|
||||||
return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, label: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, label: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, action: {
|
||||||
openDoNotTranslate()
|
openDoNotTranslate()
|
||||||
|
|
@ -342,6 +342,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||||
private let requestDeactivateSearch: () -> Void
|
private let requestDeactivateSearch: () -> Void
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
private let push: (ViewController) -> Void
|
private let push: (ViewController) -> Void
|
||||||
|
private var focusOnItemTag: LocalizationListEntryTag?
|
||||||
|
|
||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
let _ready = ValuePromise<Bool>()
|
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.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataValue.set(.single(presentationData))
|
self.presentationDataValue.set(.single(presentationData))
|
||||||
|
|
@ -376,6 +377,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||||
self.requestDeactivateSearch = requestDeactivateSearch
|
self.requestDeactivateSearch = requestDeactivateSearch
|
||||||
self.present = present
|
self.present = present
|
||||||
self.push = push
|
self.push = push
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: presentationData.theme.list.blocksBackgroundColor, direction: true)
|
self.listNode.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: presentationData.theme.list.blocksBackgroundColor, direction: true)
|
||||||
|
|
@ -741,6 +743,16 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||||
if !strongSelf.didSetReady {
|
if !strongSelf.didSetReady {
|
||||||
strongSelf.didSetReady = true
|
strongSelf.didSetReady = true
|
||||||
strongSelf._ready.set(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
|
pushControllerImpl = { [weak controller] c in
|
||||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
(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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,12 @@ private enum NotificationsPeerCategorySection: Int32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
||||||
|
case edit
|
||||||
case enable
|
case enable
|
||||||
case previews
|
case previews
|
||||||
case sound
|
case sound
|
||||||
|
case important
|
||||||
|
case deleteExceptions
|
||||||
|
|
||||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
if let other = other as? NotificationsPeerCategoryEntryTag, self == other {
|
if let other = other as? NotificationsPeerCategoryEntryTag, self == other {
|
||||||
|
|
@ -284,7 +287,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||||
case let .enableImportant(_, text, value):
|
case let .enableImportant(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||||
arguments.updateEnabledImportant(updatedValue)
|
arguments.updateEnabledImportant(updatedValue)
|
||||||
}, tag: self.tag)
|
}, tag: NotificationsPeerCategoryEntryTag.important)
|
||||||
case let .importantInfo(_, text):
|
case let .importantInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .optionsHeader(_, text):
|
case let .optionsHeader(_, text):
|
||||||
|
|
@ -314,7 +317,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||||
case let .removeAllExceptions(theme, text):
|
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: {
|
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
|
||||||
arguments.removeAllExceptions()
|
arguments.removeAllExceptions()
|
||||||
})
|
}, tag: NotificationsPeerCategoryEntryTag.deleteExceptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -617,6 +620,12 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
||||||
updatedMode(result.mode)
|
updatedMode(result.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if focusOnItemTag == .edit {
|
||||||
|
updateState {
|
||||||
|
$0.withUpdatedEditing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
|
let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
|
||||||
return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: sound) |> deliverOnMainQueue
|
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
|
pushControllerImpl = { [weak controller] c in
|
||||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
(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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,13 +203,19 @@ private func blockedPeersControllerEntries(presentationData: PresentationData, s
|
||||||
return entries
|
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 statePromise = ValuePromise(BlockedPeersControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: BlockedPeersControllerState())
|
let stateValue = Atomic(value: BlockedPeersControllerState())
|
||||||
let updateState: ((BlockedPeersControllerState) -> BlockedPeersControllerState) -> Void = { f in
|
let updateState: ((BlockedPeersControllerState) -> BlockedPeersControllerState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if forceEdit {
|
||||||
|
updateState {
|
||||||
|
$0.withUpdatedEditing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ private final class DataPrivacyControllerArguments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum PrivacyAndSecuritySection: Int32 {
|
private enum DataPrivacySection: Int32 {
|
||||||
case contacts
|
case contacts
|
||||||
case frequentContacts
|
case frequentContacts
|
||||||
case chats
|
case chats
|
||||||
|
|
@ -45,7 +45,24 @@ private enum PrivacyAndSecuritySection: Int32 {
|
||||||
case bots
|
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 contactsHeader(PresentationTheme, String)
|
||||||
case deleteContacts(PresentationTheme, String, Bool)
|
case deleteContacts(PresentationTheme, String, Bool)
|
||||||
case syncContacts(PresentationTheme, String, Bool)
|
case syncContacts(PresentationTheme, String, Bool)
|
||||||
|
|
@ -70,17 +87,17 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .contactsHeader, .deleteContacts, .syncContacts, .syncContactsInfo:
|
case .contactsHeader, .deleteContacts, .syncContacts, .syncContactsInfo:
|
||||||
return PrivacyAndSecuritySection.contacts.rawValue
|
return DataPrivacySection.contacts.rawValue
|
||||||
case .frequentContacts, .frequentContactsInfo:
|
case .frequentContacts, .frequentContactsInfo:
|
||||||
return PrivacyAndSecuritySection.frequentContacts.rawValue
|
return DataPrivacySection.frequentContacts.rawValue
|
||||||
case .chatsHeader, .deleteCloudDrafts:
|
case .chatsHeader, .deleteCloudDrafts:
|
||||||
return PrivacyAndSecuritySection.chats.rawValue
|
return DataPrivacySection.chats.rawValue
|
||||||
case .paymentHeader, .clearPaymentInfo, .paymentInfo:
|
case .paymentHeader, .clearPaymentInfo, .paymentInfo:
|
||||||
return PrivacyAndSecuritySection.payments.rawValue
|
return DataPrivacySection.payments.rawValue
|
||||||
case .secretChatLinkPreviewsHeader, .secretChatLinkPreviews, .secretChatLinkPreviewsInfo:
|
case .secretChatLinkPreviewsHeader, .secretChatLinkPreviews, .secretChatLinkPreviewsInfo:
|
||||||
return PrivacyAndSecuritySection.secretChats.rawValue
|
return DataPrivacySection.secretChats.rawValue
|
||||||
case .botList:
|
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 {
|
switch lhs {
|
||||||
case let .contactsHeader(lhsTheme, lhsText):
|
case let .contactsHeader(lhsTheme, lhsText):
|
||||||
if case let .contactsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
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
|
return lhs.stableId < rhs.stableId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,17 +248,17 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||||
case let .deleteContacts(_, text, value):
|
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: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.deleteContacts()
|
arguments.deleteContacts()
|
||||||
})
|
}, tag: DataPrivacyEntryTag.deleteSynced)
|
||||||
case let .syncContacts(_, text, value):
|
case let .syncContacts(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||||
arguments.updateSyncContacts(updatedValue)
|
arguments.updateSyncContacts(updatedValue)
|
||||||
})
|
}, tag: DataPrivacyEntryTag.syncContacts)
|
||||||
case let .syncContactsInfo(_, text):
|
case let .syncContactsInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .frequentContacts(_, text, value):
|
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
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||||
arguments.updateSuggestFrequentContacts(updatedValue)
|
arguments.updateSuggestFrequentContacts(updatedValue)
|
||||||
})
|
}, tag: DataPrivacyEntryTag.suggestContacts)
|
||||||
case let .frequentContactsInfo(_, text):
|
case let .frequentContactsInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .chatsHeader(_, text):
|
case let .chatsHeader(_, text):
|
||||||
|
|
@ -249,13 +266,13 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||||
case let .deleteCloudDrafts(_, text, value):
|
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: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.deleteCloudDrafts()
|
arguments.deleteCloudDrafts()
|
||||||
})
|
}, tag: DataPrivacyEntryTag.deleteCloudDrafts)
|
||||||
case let .paymentHeader(_, text):
|
case let .paymentHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .clearPaymentInfo(_, text, enabled):
|
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: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.clearPaymentInfo()
|
arguments.clearPaymentInfo()
|
||||||
})
|
}, tag: DataPrivacyEntryTag.clearPaymentInfo)
|
||||||
case let .paymentInfo(_, text):
|
case let .paymentInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .secretChatLinkPreviewsHeader(_, text):
|
case let .secretChatLinkPreviewsHeader(_, text):
|
||||||
|
|
@ -263,7 +280,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||||
case let .secretChatLinkPreviews(_, text, value):
|
case let .secretChatLinkPreviews(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||||
arguments.updateSecretChatLinkPreviews(updatedValue)
|
arguments.updateSecretChatLinkPreviews(updatedValue)
|
||||||
})
|
}, tag: DataPrivacyEntryTag.linkPreviews)
|
||||||
case let .secretChatLinkPreviewsInfo(_, text):
|
case let .secretChatLinkPreviewsInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case .botList:
|
case .botList:
|
||||||
|
|
@ -289,8 +306,8 @@ private struct DataPrivacyControllerState: Equatable {
|
||||||
var deletingCloudDrafts: Bool = false
|
var deletingCloudDrafts: Bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dataPrivacyControllerEntries(presentationData: PresentationData, state: DataPrivacyControllerState, secretChatLinkPreviews: Bool?, synchronizeDeviceContacts: Bool, frequentContacts: Bool, hasBotSettings: Bool) -> [PrivacyAndSecurityEntry] {
|
private func dataPrivacyControllerEntries(presentationData: PresentationData, state: DataPrivacyControllerState, secretChatLinkPreviews: Bool?, synchronizeDeviceContacts: Bool, frequentContacts: Bool, hasBotSettings: Bool) -> [DataPrivacyEntry] {
|
||||||
var entries: [PrivacyAndSecurityEntry] = []
|
var entries: [DataPrivacyEntry] = []
|
||||||
|
|
||||||
entries.append(.contactsHeader(presentationData.theme, presentationData.strings.Privacy_ContactsTitle))
|
entries.append(.contactsHeader(presentationData.theme, presentationData.strings.Privacy_ContactsTitle))
|
||||||
entries.append(.deleteContacts(presentationData.theme, presentationData.strings.Privacy_ContactsReset, !state.deletingContacts))
|
entries.append(.deleteContacts(presentationData.theme, presentationData.strings.Privacy_ContactsReset, !state.deletingContacts))
|
||||||
|
|
@ -317,7 +334,7 @@ private func dataPrivacyControllerEntries(presentationData: PresentationData, st
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dataPrivacyController(context: AccountContext) -> ViewController {
|
public func dataPrivacyController(context: AccountContext, focusOnItemTag: DataPrivacyEntryTag? = nil) -> ViewController {
|
||||||
let statePromise = ValuePromise(DataPrivacyControllerState(), ignoreRepeated: true)
|
let statePromise = ValuePromise(DataPrivacyControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: DataPrivacyControllerState())
|
let stateValue = Atomic(value: DataPrivacyControllerState())
|
||||||
let updateState: ((DataPrivacyControllerState) -> DataPrivacyControllerState) -> Void = { f in
|
let updateState: ((DataPrivacyControllerState) -> DataPrivacyControllerState) -> Void = { f in
|
||||||
|
|
@ -434,7 +451,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
||||||
}
|
}
|
||||||
if canBegin {
|
if canBegin {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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
|
var begin = false
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
|
|
@ -461,7 +478,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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 }))
|
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
|
}, updateSyncContacts: { value in
|
||||||
let _ = context.engine.contacts.updateIsContactSynchronizationEnabled(isContactSynchronizationEnabled: value).start()
|
let _ = context.engine.contacts.updateIsContactSynchronizationEnabled(isContactSynchronizationEnabled: value).start()
|
||||||
|
|
@ -556,7 +573,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
||||||
|
|
||||||
let animateChanges = false
|
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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -572,5 +589,19 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
||||||
controller?.push(c)
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,18 @@ private enum GlobalAutoremoveSection: Int32 {
|
||||||
case general
|
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 {
|
private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||||
case header
|
case header
|
||||||
case sectionHeader(String)
|
case sectionHeader(String)
|
||||||
|
|
@ -126,7 +138,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||||
case let .customAction(text):
|
case let .customAction(text):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openCustomValue()
|
arguments.openCustomValue()
|
||||||
})
|
}, tag: GlobalAutoremoveEntryTag.setCustom)
|
||||||
case let .info(text):
|
case let .info(text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||||
arguments.infoLinkAction()
|
arguments.infoLinkAction()
|
||||||
|
|
@ -188,7 +200,7 @@ private func globalAutoremoveScreenEntries(presentationData: PresentationData, s
|
||||||
return entries
|
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(
|
let initialState = GlobalAutoremoveScreenState(
|
||||||
additionalValues: Set([initialValue]),
|
additionalValues: Set([initialValue]),
|
||||||
updatedValue: initialValue
|
updatedValue: initialValue
|
||||||
|
|
@ -430,7 +442,7 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32,
|
||||||
let animateChanges = false
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -461,5 +473,19 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32,
|
||||||
controller?.dismiss()
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,19 @@ private enum IncomingMessagePrivacySection: Int32 {
|
||||||
case exceptions
|
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 {
|
private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||||
case header
|
case header
|
||||||
case optionEverybody(value: GlobalPrivacySettings.NonContactChatsPrivacy)
|
case optionEverybody(value: GlobalPrivacySettings.NonContactChatsPrivacy)
|
||||||
|
|
@ -166,7 +179,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||||
case let .exceptions(count):
|
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: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.Privacy_Messages_RemoveFee, label: count > 0 ? "\(count)" : "", sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openExceptions()
|
arguments.openExceptions()
|
||||||
})
|
}, tag: IncomingMessagePrivacyEntryTag.removeFee)
|
||||||
case .exceptionsInfo:
|
case .exceptionsInfo:
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_RemoveFeeInfo), sectionId: self.section)
|
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
|
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] = [:]
|
var disableFor: [EnginePeer.Id: SelectivePrivacyPeer] = [:]
|
||||||
if case let .enableContacts(value, _, _, _) = exceptions {
|
if case let .enableContacts(value, _, _, _) = exceptions {
|
||||||
disableFor = value
|
disableFor = value
|
||||||
|
|
@ -424,7 +437,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
|
||||||
let animateChanges = false
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -468,5 +481,19 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
|
||||||
controller?.dismiss()
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,21 @@ private enum PasscodeOptionsSection: Int32 {
|
||||||
case options
|
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 {
|
private enum PasscodeOptionsEntry: ItemListNodeEntry {
|
||||||
case togglePasscode(PresentationTheme, String, Bool)
|
case togglePasscode(PresentationTheme, String, Bool)
|
||||||
case changePasscode(PresentationTheme, String)
|
case changePasscode(PresentationTheme, String)
|
||||||
|
|
@ -114,21 +129,21 @@ private enum PasscodeOptionsEntry: ItemListNodeEntry {
|
||||||
if value {
|
if value {
|
||||||
arguments.turnPasscodeOff()
|
arguments.turnPasscodeOff()
|
||||||
}
|
}
|
||||||
})
|
}, tag: PasscodeOptionsEntryTag.togglePasscode)
|
||||||
case let .changePasscode(_, title):
|
case let .changePasscode(_, title):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.changePasscode()
|
arguments.changePasscode()
|
||||||
})
|
}, tag: PasscodeOptionsEntryTag.changePasscode)
|
||||||
case let .settingInfo(_, text):
|
case let .settingInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .autoLock(_, title, value):
|
case let .autoLock(_, title, value):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.changePasscodeTimeout()
|
arguments.changePasscodeTimeout()
|
||||||
})
|
}, tag: PasscodeOptionsEntryTag.autolock)
|
||||||
case let .touchId(_, title, value):
|
case let .touchId(_, title, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.changeTouchId(value)
|
arguments.changeTouchId(value)
|
||||||
})
|
}, tag: PasscodeOptionsEntryTag.touchId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +221,7 @@ private func passcodeOptionsControllerEntries(presentationData: PresentationData
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func passcodeOptionsController(context: AccountContext) -> ViewController {
|
func passcodeOptionsController(context: AccountContext, focusOnItemTag: PasscodeOptionsEntryTag? = nil) -> ViewController {
|
||||||
let initialState = PasscodeOptionsControllerState()
|
let initialState = PasscodeOptionsControllerState()
|
||||||
|
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
|
|
@ -360,7 +375,7 @@ func passcodeOptionsController(context: AccountContext) -> ViewController {
|
||||||
|> map { presentationData, state, passcodeOptionsData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> 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 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))
|
return (controllerState, (listState, arguments))
|
||||||
} |> afterDisposed {
|
} |> afterDisposed {
|
||||||
|
|
@ -383,6 +398,20 @@ func passcodeOptionsController(context: AccountContext) -> ViewController {
|
||||||
(controller?.navigationController as? NavigationController)?.replaceTopController(c, animated: animated)
|
(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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1629,5 +1629,19 @@ public func privacyAndSecurityController(
|
||||||
pushControllerImpl?(controller, true)
|
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
|
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 {
|
private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||||
case header(SortIndex, String)
|
case header(SortIndex, String)
|
||||||
case currentSessionHeader(SortIndex, String)
|
case currentSessionHeader(SortIndex, String)
|
||||||
|
|
@ -341,7 +355,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||||
case let .terminateOtherSessions(_, text):
|
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: {
|
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()
|
arguments.terminateOtherSessions()
|
||||||
})
|
}, tag: RecentSessionsEntryTag.terminateOtherSessions)
|
||||||
case let .terminateAllWebSessions(_, text):
|
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: {
|
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()
|
arguments.terminateAllWebSessions()
|
||||||
|
|
@ -403,7 +417,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
||||||
case let .ttlTimeout(_, text, value):
|
case let .ttlTimeout(_, text, value):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.setupAuthorizationTTL()
|
arguments.setupAuthorizationTTL()
|
||||||
}, tag: PrivacyAndSecurityEntryTag.accountTimeout)
|
}, tag: RecentSessionsEntryTag.autoTerminate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -560,13 +574,19 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
|
||||||
private final class RecentSessionsControllerImpl: ItemListController, RecentSessionsController {
|
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 statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: RecentSessionsControllerState())
|
let stateValue = Atomic(value: RecentSessionsControllerState())
|
||||||
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if focusOnItemTag == .edit {
|
||||||
|
updateState {
|
||||||
|
$0.withUpdatedEditing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
activeSessionsContext.loadMore()
|
activeSessionsContext.loadMore()
|
||||||
webSessionsContext.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 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))
|
return (controllerState, (listState, arguments))
|
||||||
} |> afterDisposed {
|
} |> afterDisposed {
|
||||||
|
|
@ -891,5 +911,19 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
||||||
controller?.dismiss()
|
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
|
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 {
|
private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||||
case forwardsPreviewHeader(PresentationTheme, String)
|
case forwardsPreviewHeader(PresentationTheme, String)
|
||||||
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, 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):
|
case let .disableFor(_, title, value, isEnabled):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openSelective(.main, false)
|
arguments.openSelective(.main, false)
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.neverAllow)
|
||||||
case let .enableFor(_, title, value, isEnabled):
|
case let .enableFor(_, title, value, isEnabled):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openSelective(.main, true)
|
arguments.openSelective(.main, true)
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.alwaysAllow)
|
||||||
case let .peersInfo(_, text):
|
case let .peersInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .callsP2PHeader(_, text):
|
case let .callsP2PHeader(_, text):
|
||||||
|
|
@ -632,17 +656,17 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||||
case let .callsP2PDisableFor(_, title, value):
|
case let .callsP2PDisableFor(_, title, value):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openSelective(.callP2P, false)
|
arguments.openSelective(.callP2P, false)
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.callsP2PNeverAllow)
|
||||||
case let .callsP2PEnableFor(_, title, value):
|
case let .callsP2PEnableFor(_, title, value):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openSelective(.callP2P, true)
|
arguments.openSelective(.callP2P, true)
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.callsP2PAlwaysAllow)
|
||||||
case let .callsP2PPeersInfo(_, text):
|
case let .callsP2PPeersInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .callsIntegrationEnabled(_, text, value):
|
case let .callsIntegrationEnabled(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateCallIntegrationEnabled?(value)
|
arguments.updateCallIntegrationEnabled?(value)
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.callsIntegration)
|
||||||
case let .callsIntegrationInfo(_, text):
|
case let .callsIntegrationInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .phoneDiscoveryHeader(_, text):
|
case let .phoneDiscoveryHeader(_, text):
|
||||||
|
|
@ -662,18 +686,18 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||||
case let .setPublicPhoto(theme, text):
|
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: {
|
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPhotoIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
||||||
arguments.setPublicPhoto?()
|
arguments.setPublicPhoto?()
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.photoSetPublic)
|
||||||
case let .removePublicPhoto(_, text, peer, image, completeImage):
|
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: {
|
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?()
|
arguments.removePublicPhoto?()
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.photoRemovePublic)
|
||||||
case let .publicPhotoInfo(_, text):
|
case let .publicPhotoInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||||
})
|
})
|
||||||
case let .hideReadTime(_, text, enabled, value):
|
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
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateHideReadTime?(value)
|
arguments.updateHideReadTime?(value)
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.lastSeenHideReadTime)
|
||||||
case let .hideReadTimeInfo(_, text):
|
case let .hideReadTimeInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .subscribeToPremium(_, text):
|
case let .subscribeToPremium(_, text):
|
||||||
|
|
@ -683,7 +707,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||||
case let .subscribeToPremiumInfo(_, text):
|
case let .subscribeToPremiumInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .disallowedGiftsHeader(_, text):
|
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):
|
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
|
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 {
|
if !isLocked {
|
||||||
|
|
@ -747,7 +771,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
|
||||||
if available {
|
if available {
|
||||||
arguments.displayLockedGiftsInfo()
|
arguments.displayLockedGiftsInfo()
|
||||||
}
|
}
|
||||||
})
|
}, tag: SelectivePrivacyEntryTag.giftsShowButton)
|
||||||
case let .showGiftButtonInfo(_, text):
|
case let .showGiftButtonInfo(_, text):
|
||||||
let attributedString = NSMutableAttributedString(string: text, font: Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize), textColor: presentationData.theme.list.freeTextColor)
|
let attributedString = NSMutableAttributedString(string: text, font: Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize), textColor: presentationData.theme.list.freeTextColor)
|
||||||
if let range = attributedString.string.range(of: "#") {
|
if let range = attributedString.string.range(of: "#") {
|
||||||
|
|
@ -1216,6 +1240,7 @@ public func selectivePrivacySettingsController(
|
||||||
requestPublicPhotoSetup: ((@escaping (UIImage?) -> Void) -> Void)? = nil,
|
requestPublicPhotoSetup: ((@escaping (UIImage?) -> Void) -> Void)? = nil,
|
||||||
requestPublicPhotoRemove: ((@escaping () -> Void) -> Void)? = nil,
|
requestPublicPhotoRemove: ((@escaping () -> Void) -> Void)? = nil,
|
||||||
openedFromBirthdayScreen: Bool = false,
|
openedFromBirthdayScreen: Bool = false,
|
||||||
|
focusOnItemTag: SelectivePrivacyEntryTag? = nil,
|
||||||
updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?, GlobalPrivacySettings?) -> Void
|
updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?, GlobalPrivacySettings?) -> Void
|
||||||
) -> ViewController {
|
) -> ViewController {
|
||||||
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
|
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||||
|
|
@ -1910,5 +1935,20 @@ public func selectivePrivacySettingsController(
|
||||||
dismissImpl = { [weak controller] in
|
dismissImpl = { [weak controller] in
|
||||||
controller?.dismiss()
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,19 +50,15 @@ private enum TwoStepVerificationUnlockSettingsSection: Int32 {
|
||||||
case email
|
case email
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TwoStepVerificationUnlockSettingsEntryTag: ItemListItemTag {
|
public enum TwoStepVerificationUnlockSettingsEntryTag: ItemListItemTag {
|
||||||
case password
|
case password
|
||||||
|
case change
|
||||||
|
case disable
|
||||||
|
case changeEmail
|
||||||
|
|
||||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
if let other = other as? TwoStepVerificationUnlockSettingsEntryTag {
|
if let other = other as? TwoStepVerificationUnlockSettingsEntryTag, self == other {
|
||||||
switch self {
|
return true
|
||||||
case .password:
|
|
||||||
if case .password = other {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -159,15 +155,15 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
|
||||||
case let .changePassword(_, text):
|
case let .changePassword(_, text):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openSetupPassword()
|
arguments.openSetupPassword()
|
||||||
})
|
}, tag: TwoStepVerificationUnlockSettingsEntryTag.change)
|
||||||
case let .turnPasswordOff(_, text):
|
case let .turnPasswordOff(_, text):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openDisablePassword()
|
arguments.openDisablePassword()
|
||||||
})
|
}, tag: TwoStepVerificationUnlockSettingsEntryTag.disable)
|
||||||
case let .setupRecoveryEmail(_, text):
|
case let .setupRecoveryEmail(_, text):
|
||||||
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openSetupEmail()
|
arguments.openSetupEmail()
|
||||||
})
|
}, tag: TwoStepVerificationUnlockSettingsEntryTag.changeEmail)
|
||||||
case let .passwordInfo(_, text):
|
case let .passwordInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .pendingEmailConfirmInfo(_, text):
|
case let .pendingEmailConfirmInfo(_, text):
|
||||||
|
|
@ -283,7 +279,7 @@ public enum TwoStepVerificationUnlockSettingsControllerData: Equatable {
|
||||||
case manage(password: String, emailSet: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
|
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 initialState = TwoStepVerificationUnlockSettingsControllerState()
|
||||||
|
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
|
|
@ -463,7 +459,7 @@ public func twoStepVerificationUnlockSettingsController(context: AccountContext,
|
||||||
return state
|
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
|
}, error: { error in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
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 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))
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,22 @@ private enum ReactionNotificationSettingsSection: Int32 {
|
||||||
case options
|
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 {
|
private enum ReactionNotificationSettingsEntry: ItemListNodeEntry {
|
||||||
enum StableId: Hashable {
|
enum StableId: Hashable {
|
||||||
case categoriesHeader
|
case categoriesHeader
|
||||||
|
|
@ -169,23 +185,23 @@ private enum ReactionNotificationSettingsEntry: ItemListNodeEntry {
|
||||||
arguments.toggleMessages(value)
|
arguments.toggleMessages(value)
|
||||||
}, action: {
|
}, action: {
|
||||||
arguments.openMessages()
|
arguments.openMessages()
|
||||||
})
|
}, tag: ReactionNotificationSettingsEntryTag.messages)
|
||||||
case let .stories(title, text, value):
|
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
|
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)
|
arguments.toggleStories(value)
|
||||||
}, action: {
|
}, action: {
|
||||||
arguments.openStories()
|
arguments.openStories()
|
||||||
})
|
}, tag: ReactionNotificationSettingsEntryTag.stories)
|
||||||
case let .optionsHeader(text):
|
case let .optionsHeader(text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .previews(text, value):
|
case let .previews(text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updatePreviews(value)
|
arguments.updatePreviews(value)
|
||||||
})
|
}, tag: ReactionNotificationSettingsEntryTag.showSender)
|
||||||
case let .sound(text, value, sound):
|
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: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||||
arguments.openSound(sound)
|
arguments.openSound(sound)
|
||||||
}, tag: self.tag)
|
}, tag: ReactionNotificationSettingsEntryTag.sound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +271,8 @@ private struct ReactionNotificationSettingsState: Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reactionNotificationSettingsController(
|
public func reactionNotificationSettingsController(
|
||||||
context: AccountContext
|
context: AccountContext,
|
||||||
|
focusOnItemTag: ReactionNotificationSettingsEntryTag? = nil
|
||||||
) -> ViewController {
|
) -> ViewController {
|
||||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
@ -399,7 +416,7 @@ public func reactionNotificationSettingsController(
|
||||||
let title: String = presentationData.strings.Notifications_Reactions_Title
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|
@ -411,5 +428,20 @@ public func reactionNotificationSettingsController(
|
||||||
pushControllerImpl = { [weak controller] c in
|
pushControllerImpl = { [weak controller] c in
|
||||||
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
(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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ extension SettingsSearchableItemIcon {
|
||||||
return PresentationResourcesSettings.support
|
return PresentationResourcesSettings.support
|
||||||
case .faq:
|
case .faq:
|
||||||
return PresentationResourcesSettings.faq
|
return PresentationResourcesSettings.faq
|
||||||
|
case .tips:
|
||||||
|
return PresentationResourcesSettings.tips
|
||||||
case .chatFolders:
|
case .chatFolders:
|
||||||
return PresentationResourcesSettings.chatFolders
|
return PresentationResourcesSettings.chatFolders
|
||||||
case .deleteAccount:
|
case .deleteAccount:
|
||||||
|
|
@ -56,24 +58,42 @@ extension SettingsSearchableItemIcon {
|
||||||
return PresentationResourcesSettings.devices
|
return PresentationResourcesSettings.devices
|
||||||
case .premium:
|
case .premium:
|
||||||
return PresentationResourcesSettings.premium
|
return PresentationResourcesSettings.premium
|
||||||
|
case .business:
|
||||||
|
return PresentationResourcesSettings.business
|
||||||
|
case .stars:
|
||||||
|
return PresentationResourcesSettings.stars
|
||||||
|
case .ton:
|
||||||
|
return PresentationResourcesSettings.ton
|
||||||
case .stories:
|
case .stories:
|
||||||
return PresentationResourcesSettings.stories
|
return PresentationResourcesSettings.stories
|
||||||
|
case .myProfile:
|
||||||
|
return PresentationResourcesSettings.myProfile
|
||||||
|
case .gift:
|
||||||
|
return PresentationResourcesSettings.premiumGift
|
||||||
|
case .powerSaving:
|
||||||
|
return PresentationResourcesSettings.powerSaving
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SettingsSearchInteraction {
|
final class SettingsSearchInteraction {
|
||||||
let openItem: (SettingsSearchableItem) -> Void
|
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.openItem = openItem
|
||||||
|
self.openItemContextMenu = openItemContextMenu
|
||||||
self.deleteRecentItem = deleteRecentItem
|
self.deleteRecentItem = deleteRecentItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SettingsSearchEntryStableId: Hashable {
|
private enum SettingsSearchEntryStableId: Hashable {
|
||||||
case result(SettingsSearchableItemId)
|
case result(AnyHashable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SettingsSearchEntry: Comparable, Identifiable {
|
private enum SettingsSearchEntry: Comparable, Identifiable {
|
||||||
|
|
@ -132,7 +152,7 @@ private func preparedSettingsSearchContainerTransition(theme: PresentationTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SettingsSearchRecentEntryStableId: Hashable {
|
private enum SettingsSearchRecentEntryStableId: Hashable {
|
||||||
case recent(SettingsSearchableItemId)
|
case recent(AnyHashable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SettingsSearchRecentEntry: Comparable, Identifiable {
|
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 {
|
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: SettingsSearchInteraction) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .recent(_, item, header):
|
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)
|
interaction.openItem(item)
|
||||||
}, deleted: {
|
}, deleted: {
|
||||||
interaction.deleteRecentItem(item.id)
|
interaction.deleteRecentItem(item.id)
|
||||||
|
|
@ -244,7 +268,19 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
private let presentationDataPromise: Promise<PresentationData>
|
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 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataPromise = Promise(self.presentationData)
|
self.presentationDataPromise = Promise(self.presentationData)
|
||||||
|
|
@ -273,15 +309,30 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
||||||
self.addSubnode(self.listNode)
|
self.addSubnode(self.listNode)
|
||||||
self.view.addSubview(self.edgeEffectView)
|
self.view.addSubview(self.edgeEffectView)
|
||||||
|
|
||||||
let interaction = SettingsSearchInteraction(openItem: { result in
|
let interaction = SettingsSearchInteraction(
|
||||||
addRecentSettingsSearchItem(engine: context.engine, item: result.id)
|
openItem: { item in
|
||||||
openResult(result)
|
addRecentSettingsSearchItem(engine: context.engine, item: item.id)
|
||||||
}, deleteRecentItem: { id in
|
openResult(item)
|
||||||
removeRecentSettingsSearchItem(engine: context.engine, item: id)
|
},
|
||||||
})
|
openItemContextMenu: { item, node, rect, gesture in
|
||||||
|
openContextMenu(item, node, rect, gesture)
|
||||||
|
},
|
||||||
|
deleteRecentItem: { id in
|
||||||
|
removeRecentSettingsSearchItem(engine: context.engine, item: id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
let searchableItems = Promise<[SettingsSearchableItem]>()
|
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]>()
|
let faqItems = Promise<[SettingsSearchableItem]>()
|
||||||
faqItems.set(faqSearchableItems(context: context, resolvedUrl: resolvedFaqUrl, suggestAccountDeletion: false))
|
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 results = searchSettingsItems(items: searchableItems, query: query)
|
||||||
let faqResults = searchSettingsItems(items: faqSearchableItems, query: query)
|
let faqResults = searchSettingsItems(items: faqSearchableItems, query: query)
|
||||||
let finalResults: [SettingsSearchableItem]
|
let finalResults: [SettingsSearchableItem]
|
||||||
if faqResults.first?.id == .faq(1) {
|
//if faqResults.first?.id == .faq(1) {
|
||||||
finalResults = faqResults + results
|
// finalResults = faqResults + results
|
||||||
} else {
|
//} else {
|
||||||
finalResults = results + faqResults
|
finalResults = results + faqResults
|
||||||
}
|
//}
|
||||||
return .single((query, finalResults))
|
return .single((query, finalResults))
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
|
@ -308,12 +359,12 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
||||||
|
|
||||||
self.recentListNode.isHidden = false
|
self.recentListNode.isHidden = false
|
||||||
|
|
||||||
let previousRecentlySearchedItemOrder = Atomic<[SettingsSearchableItemId]>(value: [])
|
let previousRecentlySearchedItemOrder = Atomic<[AnyHashable]>(value: [])
|
||||||
let fixedRecentlySearchedItems = settingsSearchRecentItems(engine: context.engine)
|
let fixedRecentlySearchedItems = settingsSearchRecentItems(engine: context.engine)
|
||||||
|> map { recentIds -> [SettingsSearchableItemId] in
|
|> map { recentIds -> [AnyHashable] in
|
||||||
var result: [SettingsSearchableItemId] = []
|
var result: [AnyHashable] = []
|
||||||
let _ = previousRecentlySearchedItemOrder.modify { current in
|
let _ = previousRecentlySearchedItemOrder.modify { current in
|
||||||
var updated: [SettingsSearchableItemId] = []
|
var updated: [AnyHashable] = []
|
||||||
for id in current {
|
for id in current {
|
||||||
inner: for recentId in recentIds {
|
inner: for recentId in recentIds {
|
||||||
if recentId == id {
|
if recentId == id {
|
||||||
|
|
@ -336,7 +387,7 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
||||||
|
|
||||||
let recentSearchItems = combineLatest(searchableItems.get(), fixedRecentlySearchedItems)
|
let recentSearchItems = combineLatest(searchableItems.get(), fixedRecentlySearchedItems)
|
||||||
|> map { searchableItems, recentItems -> [SettingsSearchableItem] in
|
|> 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
|
var map = map
|
||||||
map[item.id] = item
|
map[item.id] = item
|
||||||
return map
|
return map
|
||||||
|
|
@ -344,10 +395,10 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo
|
||||||
var result: [SettingsSearchableItem] = []
|
var result: [SettingsSearchableItem] = []
|
||||||
for itemId in recentItems {
|
for itemId in recentItems {
|
||||||
if let searchItem = searchableItemsMap[itemId] {
|
if let searchItem = searchableItemsMap[itemId] {
|
||||||
if case let .language(id) = searchItem.id, id > 0 {
|
//if case let .language(id) = searchItem.id, id > 0 {
|
||||||
} else {
|
//} else {
|
||||||
result.append(searchItem)
|
result.append(searchItem)
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
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()
|
self?.cancel()
|
||||||
}, fieldStyle: placeholderNode.fieldStyle)
|
}, 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()))
|
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
|
var height: CGFloat = 30.0 + titleLayout.size.height
|
||||||
if subtitle.isEmpty {
|
if !subtitle.isEmpty {
|
||||||
height += 22.0
|
height += 9.0
|
||||||
} else {
|
|
||||||
height += 39.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: height)
|
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))
|
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
|
return (nodeLayout, { [weak self] in
|
||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
if currentItem?.theme !== item.theme {
|
if currentItem?.theme !== item.theme {
|
||||||
|
|
@ -215,7 +215,7 @@ class SettingsSearchRecentItemNode: ItemListRevealOptionsItemNode {
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = subtitleApply()
|
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.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)
|
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
|
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.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.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||||
strongSelf.separatorNode.isHidden = last
|
strongSelf.separatorNode.isHidden = last
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,28 +38,34 @@ public final class RecentSettingsSearchQueryItem: Codable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRecentSettingsSearchItem(engine: TelegramEngine, item: SettingsSearchableItemId) {
|
func addRecentSettingsSearchItem(engine: TelegramEngine, item: AnyHashable) {
|
||||||
let itemId = SettingsSearchRecentQueryItemId(item.index)
|
guard let id = item.base as? String, let data = id.data(using: .ascii) else {
|
||||||
let _ = engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId.rawValue, item: RecentSettingsSearchQueryItem(), removeTailIfCountExceeds: 100).start()
|
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) {
|
func removeRecentSettingsSearchItem(engine: TelegramEngine, item: AnyHashable) {
|
||||||
let itemId = SettingsSearchRecentQueryItemId(item.index)
|
guard let id = item.base as? String, let data = id.data(using: .ascii) else {
|
||||||
let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId.rawValue).start()
|
return
|
||||||
|
}
|
||||||
|
let itemId = MemoryBuffer(data: data)
|
||||||
|
let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearRecentSettingsSearchItems(engine: TelegramEngine) {
|
func clearRecentSettingsSearchItems(engine: TelegramEngine) {
|
||||||
let _ = engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems).start()
|
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))
|
return engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems))
|
||||||
|> map { items -> [SettingsSearchableItemId] in
|
|> map { items -> [AnyHashable] in
|
||||||
var result: [SettingsSearchableItemId] = []
|
var result: [AnyHashable] = []
|
||||||
for item in items {
|
for item in items {
|
||||||
let index = SettingsSearchRecentQueryItemId(item.id).value
|
let data = item.id.makeData()
|
||||||
if let itemId = SettingsSearchableItemId(index: index) {
|
if let id = String(data: data, encoding: .utf8) {
|
||||||
result.append(itemId)
|
result.append(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,15 @@ private let titleFont = Font.regular(17.0)
|
||||||
private let subtitleFont = Font.regular(13.0)
|
private let subtitleFont = Font.regular(13.0)
|
||||||
|
|
||||||
class SettingsSearchResultItemNode: ListViewItemNode {
|
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 backgroundNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
@ -90,6 +99,15 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
||||||
private var layoutParams: (ListViewItemLayoutParams, ItemListNeighbors)?
|
private var layoutParams: (ListViewItemLayoutParams, ItemListNeighbors)?
|
||||||
|
|
||||||
init() {
|
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 = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
|
|
@ -119,9 +137,48 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
||||||
|
|
||||||
super.init(layerBacked: false, rotated: false, seeThrough: false)
|
super.init(layerBacked: false, rotated: false, seeThrough: false)
|
||||||
|
|
||||||
self.addSubnode(self.iconNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.addSubnode(self.subtitleNode)
|
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) {
|
func asyncLayout() -> (_ item: SettingsSearchResultItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||||
|
|
@ -132,7 +189,7 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
||||||
|
|
||||||
return { item, params, neighbors in
|
return { item, params, neighbors in
|
||||||
var leftInset: CGFloat = params.leftInset
|
var leftInset: CGFloat = params.leftInset
|
||||||
let contentInset: CGFloat = 58.0
|
let contentInset: CGFloat = 60.0
|
||||||
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||||
|
|
||||||
|
|
@ -157,12 +214,11 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
||||||
updateIconImage = item.icon
|
updateIconImage = item.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
var height = titleLayout.size.height
|
var height: CGFloat = 30.0 + titleLayout.size.height
|
||||||
if subtitle.isEmpty {
|
if !subtitle.isEmpty {
|
||||||
height += 22.0
|
height += 9.0
|
||||||
} else {
|
|
||||||
height += 39.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: height)
|
let contentSize = CGSize(width: params.width, height: height)
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
return (layout, { [weak self] animated in
|
return (layout, { [weak self] animated in
|
||||||
|
|
@ -219,11 +275,29 @@ class SettingsSearchResultItemNode: ListViewItemNode {
|
||||||
default:
|
default:
|
||||||
bottomStripeInset = 0.0
|
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.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.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))
|
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.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))
|
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
|
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 statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: ArchivedStickerPacksControllerState())
|
let stateValue = Atomic(value: ArchivedStickerPacksControllerState())
|
||||||
let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in
|
let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if forceEdit {
|
||||||
|
updateState {
|
||||||
|
$0.withUpdatedEditing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
var navigationControllerImpl: (() -> NavigationController?)?
|
var navigationControllerImpl: (() -> NavigationController?)?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,10 @@ private enum InstalledStickerPacksSection: Int32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum InstalledStickerPacksEntryTag: ItemListItemTag {
|
public enum InstalledStickerPacksEntryTag: ItemListItemTag {
|
||||||
|
case edit
|
||||||
case suggestOptions
|
case suggestOptions
|
||||||
|
case largeEmoji
|
||||||
|
case dynamicOrder
|
||||||
|
|
||||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
if let other = other as? InstalledStickerPacksEntryTag, self == other {
|
if let other = other as? InstalledStickerPacksEntryTag, self == other {
|
||||||
|
|
@ -397,7 +400,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
|
||||||
case let .largeEmoji(_, text, value):
|
case let .largeEmoji(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.toggleLargeEmoji(value)
|
arguments.toggleLargeEmoji(value)
|
||||||
})
|
}, tag: InstalledStickerPacksEntryTag.largeEmoji)
|
||||||
case let .trending(theme, text, count):
|
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: {
|
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()
|
arguments.openFeatured()
|
||||||
|
|
@ -421,7 +424,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
|
||||||
case let .packOrder(_, text, value):
|
case let .packOrder(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.toggleDynamicPackOrder(value)
|
arguments.toggleDynamicPackOrder(value)
|
||||||
})
|
}, tag: InstalledStickerPacksEntryTag.dynamicOrder)
|
||||||
case let .packOrderInfo(_, text):
|
case let .packOrderInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .suggestAnimatedEmoji(text, value):
|
case let .suggestAnimatedEmoji(text, value):
|
||||||
|
|
@ -651,6 +654,12 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if focusOnItemTag == InstalledStickerPacksEntryTag.edit {
|
||||||
|
updateState {
|
||||||
|
$0.withUpdatedEditing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
if let forceTheme {
|
if let forceTheme {
|
||||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||||
|
|
@ -1299,6 +1308,20 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||||
controller?.dismiss()
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,18 @@ import WallpaperBackgroundNode
|
||||||
import AnimationCache
|
import AnimationCache
|
||||||
import MultiAnimationRenderer
|
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? {
|
private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
|
return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
@ -33,6 +45,8 @@ private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||||
|
|
||||||
private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let focusOnItemTag: TextSizeSelectionEntryTag?
|
||||||
|
|
||||||
private var presentationThemeSettings: PresentationThemeSettings
|
private var presentationThemeSettings: PresentationThemeSettings
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
|
|
@ -57,8 +71,9 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
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.context = context
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.presentationThemeSettings = presentationThemeSettings
|
self.presentationThemeSettings = presentationThemeSettings
|
||||||
|
|
@ -92,7 +107,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
||||||
self.chatBackgroundNode.update(wallpaper: self.presentationData.chatWallpaper, animated: false)
|
self.chatBackgroundNode.update(wallpaper: self.presentationData.chatWallpaper, animated: false)
|
||||||
self.chatBackgroundNode.updateBubbleTheme(bubbleTheme: self.presentationData.theme, bubbleCorners: self.presentationData.chatBubbleCorners)
|
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 = ASImageNode()
|
||||||
self.maskNode.displaysAsynchronously = false
|
self.maskNode.displaysAsynchronously = false
|
||||||
|
|
@ -602,6 +617,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
||||||
|
|
||||||
final class TextSizeSelectionController: ViewController {
|
final class TextSizeSelectionController: ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let focusOnItemTag: TextSizeSelectionEntryTag?
|
||||||
|
|
||||||
private var controllerNode: TextSizeSelectionControllerNode {
|
private var controllerNode: TextSizeSelectionControllerNode {
|
||||||
return self.displayNode as! TextSizeSelectionControllerNode
|
return self.displayNode as! TextSizeSelectionControllerNode
|
||||||
|
|
@ -618,8 +634,9 @@ final class TextSizeSelectionController: ViewController {
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
private var applyDisposable = MetaDisposable()
|
private var applyDisposable = MetaDisposable()
|
||||||
|
|
||||||
public init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings) {
|
public init(context: AccountContext, presentationThemeSettings: PresentationThemeSettings, focusOnItemTag: TextSizeSelectionEntryTag? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.focusOnItemTag = focusOnItemTag
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.presentationThemeSettings = presentationThemeSettings
|
self.presentationThemeSettings = presentationThemeSettings
|
||||||
|
|
@ -670,7 +687,7 @@ final class TextSizeSelectionController: ViewController {
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
super.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 {
|
if let strongSelf = self {
|
||||||
strongSelf.dismiss()
|
strongSelf.dismiss()
|
||||||
}
|
}
|
||||||
|
|
@ -727,7 +744,7 @@ private final class TextSelectionToolbarNode: ASDisplayNode {
|
||||||
var updateUseSystemFont: ((Bool) -> Void)?
|
var updateUseSystemFont: ((Bool) -> Void)?
|
||||||
var updateCustomFontSize: ((PresentationFontSize) -> Void)?
|
var updateCustomFontSize: ((PresentationFontSize) -> Void)?
|
||||||
|
|
||||||
init(presentationThemeSettings: PresentationThemeSettings, presentationData: PresentationData) {
|
init(presentationThemeSettings: PresentationThemeSettings, presentationData: PresentationData, focusOnItemTag: TextSizeSelectionEntryTag?) {
|
||||||
self.presentationThemeSettings = presentationThemeSettings
|
self.presentationThemeSettings = presentationThemeSettings
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
|
@ -774,6 +791,10 @@ private final class TextSelectionToolbarNode: ASDisplayNode {
|
||||||
|
|
||||||
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
||||||
self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside)
|
self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
|
if focusOnItemTag == .useSystem {
|
||||||
|
self.switchItemNode.displayHighlight()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDoneEnabled(_ enabled: Bool) {
|
func setDoneEnabled(_ enabled: Bool) {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,8 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
|
||||||
case powerSaving
|
case powerSaving
|
||||||
case stickersAndEmoji
|
case stickersAndEmoji
|
||||||
case animations
|
case animations
|
||||||
|
case tapForNextMedia
|
||||||
|
case nightMode
|
||||||
|
|
||||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
if let other = other as? ThemeSettingsEntryTag, self == other {
|
if let other = other as? ThemeSettingsEntryTag, self == other {
|
||||||
|
|
@ -318,7 +320,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||||
case let .autoNight(_, title, value, enabled):
|
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
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.toggleNightTheme(value)
|
arguments.toggleNightTheme(value)
|
||||||
}, tag: nil)
|
}, tag: ThemeSettingsEntryTag.nightMode)
|
||||||
case let .autoNightTheme(_, text, value):
|
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: {
|
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||||
arguments.openAutoNightTheme()
|
arguments.openAutoNightTheme()
|
||||||
|
|
@ -352,7 +354,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||||
case let .showNextMediaOnTap(_, title, value):
|
case let .showNextMediaOnTap(_, title, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.toggleShowNextMediaOnTap(value)
|
arguments.toggleShowNextMediaOnTap(value)
|
||||||
}, tag: ThemeSettingsEntryTag.animations)
|
}, tag: ThemeSettingsEntryTag.tapForNextMedia)
|
||||||
case let .showNextMediaOnTapInfo(_, text):
|
case let .showNextMediaOnTapInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
}
|
}
|
||||||
|
|
@ -1309,6 +1311,21 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||||
presentCrossfadeControllerImpl?(true)
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,10 +91,10 @@ private final class SheetContent: CombinedComponent {
|
||||||
|
|
||||||
let closeButton = closeButton.update(
|
let closeButton = closeButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: theme.overallDarkAppearance,
|
isDark: theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
|
|
@ -105,7 +105,7 @@ private final class SheetContent: CombinedComponent {
|
||||||
component.dismiss()
|
component.dismiss()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(closeButton
|
context.add(closeButton
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ swift_library(
|
||||||
"//submodules/Pasteboard:Pasteboard",
|
"//submodules/Pasteboard:Pasteboard",
|
||||||
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController",
|
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController",
|
||||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
|
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,13 @@ import MultiAnimationRenderer
|
||||||
import Pasteboard
|
import Pasteboard
|
||||||
import StickerPackEditTitleController
|
import StickerPackEditTitleController
|
||||||
import EntityKeyboard
|
import EntityKeyboard
|
||||||
//import CameraScreen
|
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
|
import EdgeEffect
|
||||||
|
import GlassBarButtonComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
import LottieComponent
|
||||||
|
import ButtonComponent
|
||||||
|
|
||||||
private let maxStickersCount = 120
|
private let maxStickersCount = 120
|
||||||
|
|
||||||
|
|
@ -143,18 +147,20 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
private let previewIconFile: TelegramMediaFile?
|
private let previewIconFile: TelegramMediaFile?
|
||||||
private var mainPreviewIcon: ComponentView<Empty>?
|
private var mainPreviewIcon: ComponentView<Empty>?
|
||||||
private let gridNode: GridNode
|
private let gridNode: GridNode
|
||||||
private let actionAreaBackgroundNode: NavigationBackgroundNode
|
|
||||||
private let actionAreaSeparatorNode: ASDisplayNode
|
|
||||||
private let buttonNode: HighlightableButtonNode
|
private let buttonNode: HighlightableButtonNode
|
||||||
private let titleBackgroundnode: NavigationBackgroundNode
|
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private var titlePlaceholderNode: ShimmerEffectNode?
|
private var titlePlaceholderNode: ShimmerEffectNode?
|
||||||
private let titleContainer: ASDisplayNode
|
private let titleContainer: ASDisplayNode
|
||||||
private let titleSeparatorNode: ASDisplayNode
|
|
||||||
|
|
||||||
private let topContainerNode: ASDisplayNode
|
private let topContainerNode: ASDisplayNode
|
||||||
private let cancelButtonNode: HighlightableButtonNode
|
private let bottomContainerNode: ASDisplayNode
|
||||||
private let moreButtonNode: MoreButtonNode
|
|
||||||
|
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)?
|
private(set) var validLayout: (ContainerViewLayout, CGRect, CGFloat, UIEdgeInsets)?
|
||||||
|
|
||||||
|
|
@ -176,6 +182,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
return self.isReadyValue.get()
|
return self.isReadyValue.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var itemCount: Int32 = 0
|
||||||
|
|
||||||
var expandProgress: CGFloat = 0.0
|
var expandProgress: CGFloat = 0.0
|
||||||
var expandScrollProgress: CGFloat = 0.0
|
var expandScrollProgress: CGFloat = 0.0
|
||||||
var modalProgress: CGFloat = 0.0
|
var modalProgress: CGFloat = 0.0
|
||||||
|
|
@ -225,7 +233,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.displaysAsynchronously = true
|
self.backgroundNode.displaysAsynchronously = true
|
||||||
self.backgroundNode.displayWithoutProcessing = 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
|
self.previewIconFile = previewIconFile
|
||||||
if self.previewIconFile != nil {
|
if self.previewIconFile != nil {
|
||||||
|
|
@ -235,14 +243,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
self.gridNode = GridNode()
|
self.gridNode = GridNode()
|
||||||
self.gridNode.scrollView.alwaysBounceVertical = true
|
self.gridNode.scrollView.alwaysBounceVertical = true
|
||||||
self.gridNode.scrollView.showsVerticalScrollIndicator = false
|
self.gridNode.scrollView.showsVerticalScrollIndicator = false
|
||||||
|
|
||||||
self.titleBackgroundnode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
self.bottomContainerNode = ASDisplayNode()
|
||||||
|
|
||||||
self.actionAreaBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
|
|
||||||
|
|
||||||
self.actionAreaSeparatorNode = ASDisplayNode()
|
|
||||||
self.actionAreaSeparatorNode.backgroundColor = self.presentationData.theme.rootController.tabBar.separatorColor
|
|
||||||
|
|
||||||
self.buttonNode = HighlightableButtonNode()
|
self.buttonNode = HighlightableButtonNode()
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
self.titleNode.textAlignment = .center
|
self.titleNode.textAlignment = .center
|
||||||
|
|
@ -261,13 +264,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.titleContainer = ASDisplayNode()
|
self.titleContainer = ASDisplayNode()
|
||||||
self.titleSeparatorNode = ASDisplayNode()
|
|
||||||
self.titleSeparatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
|
||||||
|
|
||||||
self.topContainerNode = ASDisplayNode()
|
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 addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> Void)?
|
||||||
var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)?
|
var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)?
|
||||||
|
|
@ -290,18 +287,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.gridNode)
|
self.addSubnode(self.gridNode)
|
||||||
self.addSubnode(self.actionAreaBackgroundNode)
|
|
||||||
self.addSubnode(self.actionAreaSeparatorNode)
|
|
||||||
self.addSubnode(self.buttonNode)
|
|
||||||
|
|
||||||
self.titleContainer.addSubnode(self.titleNode)
|
self.titleContainer.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.titleContainer)
|
self.addSubnode(self.titleContainer)
|
||||||
self.addSubnode(self.titleSeparatorNode)
|
|
||||||
|
|
||||||
self.addSubnode(self.topContainerNode)
|
self.addSubnode(self.topContainerNode)
|
||||||
self.topContainerNode.addSubnode(self.cancelButtonNode)
|
self.addSubnode(self.bottomContainerNode)
|
||||||
self.topContainerNode.addSubnode(self.moreButtonNode)
|
|
||||||
|
|
||||||
self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
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
|
self.gridNode.interactiveScrollingWillBeEnded = { [weak self] contentOffset, velocity, targetOffset -> CGPoint in
|
||||||
guard let strongSelf = self, !strongSelf.isDismissed else {
|
guard let strongSelf = self, !strongSelf.isDismissed else {
|
||||||
return targetOffset
|
return targetOffset
|
||||||
|
|
@ -431,27 +416,27 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
strongSelf.updateStickerPackContents(contents, hasPremium: hasPremium)
|
strongSelf.updateStickerPackContents(contents, hasPremium: hasPremium)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
// self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
// self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self {
|
// if let strongSelf = self {
|
||||||
if highlighted {
|
// if highlighted {
|
||||||
strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity")
|
// strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity")
|
||||||
strongSelf.buttonNode.alpha = 0.8
|
// strongSelf.buttonNode.alpha = 0.8
|
||||||
} else {
|
// } else {
|
||||||
strongSelf.buttonNode.alpha = 1.0
|
// strongSelf.buttonNode.alpha = 1.0
|
||||||
strongSelf.buttonNode.layer.animateAlpha(from: 0.8, to: 1.0, duration: 0.3)
|
// 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.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.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.moreButtonNode.action = { [weak self] _, gesture in
|
// self.moreButtonNode.action = { [weak self] _, gesture in
|
||||||
if let strongSelf = self {
|
// if let strongSelf = self {
|
||||||
strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
|
// strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
self.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.2)
|
self.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.2)
|
||||||
|
|
||||||
|
|
@ -510,7 +495,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
private var reorderingGestureRecognizer: ReorderingGestureRecognizer?
|
private var reorderingGestureRecognizer: ReorderingGestureRecognizer?
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
let peekGestureRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in
|
let peekGestureRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
|
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
|
reorderingGestureRecognizer.isEnabled = self.isEditing
|
||||||
self.reorderingGestureRecognizer = reorderingGestureRecognizer
|
self.reorderingGestureRecognizer = reorderingGestureRecognizer
|
||||||
self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer)
|
self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer)
|
||||||
|
|
||||||
|
self.topContainerNode.view.addSubview(self.topEdgeEffectView)
|
||||||
|
self.bottomContainerNode.view.addSubview(self.bottomEdgeEffectView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func hideMainPreviewIcon() {
|
private func hideMainPreviewIcon() {
|
||||||
|
|
@ -1005,15 +993,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
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.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.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
|
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)
|
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 {
|
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)
|
self.updateLayout(layout: layout, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1084,7 +1064,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
private func updateIsEditing(_ isEditing: Bool) {
|
private func updateIsEditing(_ isEditing: Bool) {
|
||||||
self.isEditing = isEditing
|
self.isEditing = isEditing
|
||||||
self.updateEntries(reload: true)
|
self.updateEntries(reload: true)
|
||||||
self.updateButton()
|
|
||||||
self.peekGestureRecognizer?.longPressEnabled = !isEditing
|
self.peekGestureRecognizer?.longPressEnabled = !isEditing
|
||||||
self.reorderingGestureRecognizer?.isEnabled = isEditing
|
self.reorderingGestureRecognizer?.isEnabled = isEditing
|
||||||
if let (layout, _, _, _) = self.validLayout {
|
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 {
|
guard let controller = self.controller else {
|
||||||
return
|
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)
|
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) {
|
private func updateStickerPackContents(_ contents: [LoadedStickerPack], hasPremium: Bool) {
|
||||||
self.currentContents = contents
|
self.currentContents = contents
|
||||||
self.didReceiveStickerPackResult = true
|
self.didReceiveStickerPackResult = true
|
||||||
|
|
@ -1864,7 +1751,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
} else {
|
} else {
|
||||||
count = Int32(entries.count)
|
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 {
|
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
|
insets.top += 10.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonHeight: CGFloat = 50.0
|
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.intrinsicInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||||
var actionAreaTopInset: CGFloat = 8.0
|
let titleAreaInset: CGFloat = 76.0
|
||||||
var actionAreaBottomInset: CGFloat = 16.0
|
let buttonHeight: CGFloat = 52.0
|
||||||
if let _ = self.controller?.mainActionTitle {
|
let buttonSideInset: CGFloat = 30.0
|
||||||
|
let actionAreaHeight: CGFloat = buttonHeight + buttonInsets.bottom
|
||||||
} 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 buttonSideInset: CGFloat = 16.0
|
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)))
|
||||||
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.actionAreaBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: actionAreaHeight)))
|
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)))
|
||||||
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)))
|
|
||||||
|
|
||||||
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))
|
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)
|
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))
|
//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)
|
//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.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.cancelButtonNode, alpha: self.isEditing ? 0.0 : 1.0)
|
||||||
transition.updateAlpha(node: self.moreButtonNode, 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 {
|
if firstTime {
|
||||||
while !self.enqueuedTransactions.isEmpty {
|
while !self.enqueuedTransactions.isEmpty {
|
||||||
|
|
@ -2194,7 +2208,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||||
if !transition.isAnimated {
|
if !transition.isAnimated {
|
||||||
self.backgroundNode.layer.removeAllAnimations()
|
self.backgroundNode.layer.removeAllAnimations()
|
||||||
self.titleContainer.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))
|
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)
|
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())
|
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0)), size: CGSize())
|
||||||
} else {
|
} 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)
|
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.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)))
|
transition.updateFrame(node: self.topContainerNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: 76.0)))
|
||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
|
||||||
transition.updateAlpha(node: self.titleSeparatorNode, alpha: unclippedBackgroundY < minBackgroundY ? 1.0 : 0.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func enqueueTransaction(_ transaction: StickerPackPreviewGridTransaction) {
|
private func enqueueTransaction(_ transaction: StickerPackPreviewGridTransaction) {
|
||||||
|
|
@ -2423,7 +2430,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||||
let containerInsets: UIEdgeInsets
|
let containerInsets: UIEdgeInsets
|
||||||
if case .regular = layout.metrics.widthClass {
|
if case .regular = layout.metrics.widthClass {
|
||||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.01)
|
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))
|
let size = CGSize(width: 390.0, height: min(560.0, layout.size.height - 60.0))
|
||||||
var contentRect: CGRect
|
var contentRect: CGRect
|
||||||
|
|
@ -2476,8 +2483,15 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||||
let shadowFrame = containerContainingFrame.insetBy(dx: -60.0, dy: -60.0)
|
let shadowFrame = containerContainingFrame.insetBy(dx: -60.0, dy: -60.0)
|
||||||
transition.updateFrame(node: self.shadowNode, frame: shadowFrame)
|
transition.updateFrame(node: self.shadowNode, frame: shadowFrame)
|
||||||
|
|
||||||
let expandProgress: CGFloat = 1.0
|
let expandProgress: CGFloat
|
||||||
let scaledInset: CGFloat = 12.0
|
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 scaledDistance: CGFloat = 4.0
|
||||||
let minScale = (layout.size.width - scaledInset * 2.0) / layout.size.width
|
let minScale = (layout.size.width - scaledInset * 2.0) / layout.size.width
|
||||||
let containerScale = expandProgress * 1.0 + (1.0 - expandProgress) * minScale
|
let containerScale = expandProgress * 1.0 + (1.0 - expandProgress) * minScale
|
||||||
|
|
@ -3101,15 +3115,15 @@ public func StickerPackScreen(
|
||||||
|
|
||||||
private final class StickerPackContextReferenceContentSource: ContextReferenceContentSource {
|
private final class StickerPackContextReferenceContentSource: ContextReferenceContentSource {
|
||||||
private let controller: ViewController
|
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.controller = controller
|
||||||
self.sourceNode = sourceNode
|
self.sourceView = sourceView
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
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 platform: String
|
||||||
public let ip: String
|
public let ip: String
|
||||||
public let region: 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`
|
case `default`
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public extension TelegramEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestMessageActionUrlAuth(subject: MessageActionUrlSubject) -> Signal<MessageActionUrlAuthResult, NoError> {
|
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> {
|
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)
|
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]) {
|
func removeGifts(references: [StarGiftReference]) {
|
||||||
let referencesSet = Set(references)
|
let referencesSet = Set(references)
|
||||||
self.gifts.removeAll { gift in
|
self.gifts.removeAll { gift in
|
||||||
|
|
@ -3318,6 +3324,12 @@ public final class CraftGiftsContext {
|
||||||
impl.reload()
|
impl.reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.addGift(gift: gift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func removeGifts(references: [StarGiftReference]) {
|
public func removeGifts(references: [StarGiftReference]) {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
|
|
@ -3838,6 +3850,7 @@ private final class ResaleGiftsContextImpl {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let giftId: Int64
|
private let giftId: Int64
|
||||||
|
private let forCrafting: Bool
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
|
@ -3861,11 +3874,13 @@ private final class ResaleGiftsContextImpl {
|
||||||
init(
|
init(
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
account: Account,
|
account: Account,
|
||||||
giftId: Int64
|
giftId: Int64,
|
||||||
|
forCrafting: Bool
|
||||||
) {
|
) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.account = account
|
self.account = account
|
||||||
self.giftId = giftId
|
self.giftId = giftId
|
||||||
|
self.forCrafting = forCrafting
|
||||||
|
|
||||||
self.loadMore()
|
self.loadMore()
|
||||||
}
|
}
|
||||||
|
|
@ -3898,6 +3913,10 @@ private final class ResaleGiftsContextImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
|
if self.forCrafting {
|
||||||
|
flags |= (1 << 4)
|
||||||
|
}
|
||||||
|
|
||||||
switch sorting {
|
switch sorting {
|
||||||
case .date:
|
case .date:
|
||||||
break
|
break
|
||||||
|
|
@ -4169,11 +4188,12 @@ public final class ResaleGiftsContext {
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
account: Account,
|
account: Account,
|
||||||
giftId: Int64
|
giftId: Int64,
|
||||||
|
forCrafting: Bool
|
||||||
) {
|
) {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
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 voiceMessagesResumeTrimWarning = 82
|
||||||
case globalPostsSearch = 83
|
case globalPostsSearch = 83
|
||||||
case giftAuctionTips = 84
|
case giftAuctionTips = 84
|
||||||
|
case giftCraftingTips = 85
|
||||||
|
|
||||||
var key: ValueBoxKey {
|
var key: ValueBoxKey {
|
||||||
let v = ValueBoxKey(length: 4)
|
let v = ValueBoxKey(length: 4)
|
||||||
|
|
@ -574,6 +575,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||||
static func giftAuctionTips() -> NoticeEntryKey {
|
static func giftAuctionTips() -> NoticeEntryKey {
|
||||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftAuctionTips.key)
|
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 {
|
public struct ApplicationSpecificNotice {
|
||||||
|
|
@ -2514,4 +2519,31 @@ public struct ApplicationSpecificNotice {
|
||||||
return Int(previousValue)
|
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 {
|
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)
|
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 {
|
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 {
|
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
|
let fraction = abs(Double(amount.nanos)) / 10e6
|
||||||
if fraction > 0.0 {
|
if fraction > 0.0 {
|
||||||
balanceText.append(dateTimeFormat.decimalSeparator)
|
balanceText.append(dateTimeFormat.decimalSeparator)
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
|
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
|
||||||
"//submodules/TelegramUI/Components/Gifts/GiftStoreScreen",
|
"//submodules/TelegramUI/Components/Gifts/GiftStoreScreen",
|
||||||
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
||||||
|
"//submodules/TelegramUI/Components/Gifts/GiftCraftScreen",
|
||||||
"//submodules/TelegramUI/Components/ContentReportScreen",
|
"//submodules/TelegramUI/Components/ContentReportScreen",
|
||||||
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
|
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
|
||||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||||
|
|
@ -513,7 +514,9 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/AlertComponent",
|
"//submodules/TelegramUI/Components/AlertComponent",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController",
|
"//submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController",
|
||||||
"//submodules/TelegramUI/Components/CocoonInfoScreen",
|
"//submodules/TelegramUI/Components/CocoonInfoScreen",
|
||||||
|
"//submodules/TelegramUI/Components/ProxyServerPreviewScreen",
|
||||||
"//submodules/TelegramUI/Components/ContextControllerImpl",
|
"//submodules/TelegramUI/Components/ContextControllerImpl",
|
||||||
|
"//submodules/TelegramUI/Components/AuthConfirmationScreen",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
"//build-system:ios_sim_arm64": [],
|
"//build-system:ios_sim_arm64": [],
|
||||||
|
|
|
||||||
|
|
@ -489,10 +489,10 @@ private final class RecentActionsSettingsSheetComponent: Component {
|
||||||
let leftButtonSize = self.leftButton.update(
|
let leftButtonSize = self.leftButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(GlassBarButtonComponent(
|
component: AnyComponent(GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: environment.theme.overallDarkAppearance,
|
isDark: environment.theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
|
|
@ -507,7 +507,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
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)
|
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 16.0), size: leftButtonSize)
|
||||||
if let leftButtonView = self.leftButton.view {
|
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)
|
self.containerView.update(size: availableSize, isDark: environment.theme.overallDarkAppearance, transition: transition)
|
||||||
|
|
||||||
var availableHeight = availableSize.height
|
var availableHeight = availableSize.height
|
||||||
|
availableHeight -= environment.statusBarHeight
|
||||||
if component.configuration.allowInputInset, environment.inputHeight > 0.0 {
|
if component.configuration.allowInputInset, environment.inputHeight > 0.0 {
|
||||||
availableHeight -= environment.inputHeight
|
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)
|
self.backgroundView.update(size: alertSize, cornerRadius: 35.0, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||||
|
|
||||||
return availableSize
|
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
|
import AccountContext
|
||||||
|
|
||||||
public final class AvatarComponent: Component {
|
public final class AvatarComponent: Component {
|
||||||
|
public enum ClipStyle {
|
||||||
|
case round
|
||||||
|
case roundedRect
|
||||||
|
}
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
|
let clipStyle: ClipStyle
|
||||||
let icon: AnyComponent<Empty>?
|
let icon: AnyComponent<Empty>?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
|
clipStyle: ClipStyle = .round,
|
||||||
icon: AnyComponent<Empty>? = nil
|
icon: AnyComponent<Empty>? = nil
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.clipStyle = clipStyle
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +43,9 @@ public final class AvatarComponent: Component {
|
||||||
if lhs.peer != rhs.peer {
|
if lhs.peer != rhs.peer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.clipStyle != rhs.clipStyle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.icon != rhs.icon {
|
if lhs.icon != rhs.icon {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +92,7 @@ public final class AvatarComponent: Component {
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: icon,
|
component: icon,
|
||||||
environment: {},
|
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)
|
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 {
|
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)
|
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.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
self.avatarNode.setPeer(
|
self.avatarNode.setPeer(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
peer: component.peer,
|
peer: component.peer,
|
||||||
|
clipStyle: clipStyle,
|
||||||
synchronousLoad: true,
|
synchronousLoad: true,
|
||||||
cutoutRect: cutoutRect
|
cutoutRect: cutoutRect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/AvatarBackground",
|
"//submodules/TelegramUI/Components/AvatarBackground",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
"//submodules/TelegramUI/Components/PremiumAlertController",
|
"//submodules/TelegramUI/Components/PremiumAlertController",
|
||||||
|
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import AvatarBackground
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import PremiumAlertController
|
import PremiumAlertController
|
||||||
|
import GlassBarButtonComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
|
||||||
public struct AvatarKeyboardInputData: Equatable {
|
public struct AvatarKeyboardInputData: Equatable {
|
||||||
var emoji: EmojiPagerContentComponent
|
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(
|
let navigationCancelButtonSize = self.navigationCancelButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Button(
|
component: AnyComponent(
|
||||||
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: state.expanded && !backgroundIsBright ? .white : environment.theme.rootController.navigationBar.accentTextColor)),
|
GlassBarButtonComponent(
|
||||||
action: { [weak self] in
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
guard let self else {
|
backgroundColor: nil,
|
||||||
return
|
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: {},
|
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 let navigationCancelButtonView = self.navigationCancelButton.view {
|
||||||
if navigationCancelButtonView.superview == nil {
|
if navigationCancelButtonView.superview == nil {
|
||||||
self.addSubview(navigationCancelButtonView)
|
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)
|
transition.setAlpha(view: navigationCancelButtonView, alpha: !state.editingColor ? 1.0 : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigationDoneButtonSize = self.navigationDoneButton.update(
|
let navigationDoneButtonSize = self.navigationDoneButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Button(
|
component: AnyComponent(
|
||||||
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)),
|
GlassBarButtonComponent(
|
||||||
action: { [weak self] in
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
guard let self else {
|
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
|
||||||
return
|
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: {},
|
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 let navigationDoneButtonView = self.navigationDoneButton.view {
|
||||||
if navigationDoneButtonView.superview == nil {
|
if navigationDoneButtonView.superview == nil {
|
||||||
self.addSubview(navigationDoneButtonView)
|
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)
|
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 += keyboardTitleSize.height
|
||||||
contentHeight += 8.0
|
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 {
|
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))
|
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(
|
let buttonSize = self.buttonView.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
|
|
@ -1340,13 +1355,13 @@ final class AvatarEditorScreenComponent: Component {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
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 let buttonView = self.buttonView.view {
|
||||||
if buttonView.superview == nil {
|
if buttonView.superview == nil {
|
||||||
self.addSubview(buttonView)
|
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))
|
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)])
|
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 {
|
struct CameraState: Equatable {
|
||||||
|
enum CameraMode: Int32, Equatable {
|
||||||
|
case photo
|
||||||
|
case video
|
||||||
|
case live
|
||||||
|
}
|
||||||
|
|
||||||
enum Recording: Equatable {
|
enum Recording: Equatable {
|
||||||
case none
|
case none
|
||||||
case holding
|
case holding
|
||||||
|
|
@ -548,7 +548,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||||
self.buttonPressTimestamp = nil
|
self.buttonPressTimestamp = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCameraMode(_ mode: CameraMode) {
|
func updateCameraMode(_ mode: CameraState.CameraMode) {
|
||||||
guard let controller = self.getController(), let camera = controller.camera else {
|
guard let controller = self.getController(), let camera = controller.camera else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -2102,7 +2102,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||||
availableModeControlSize = availableSize
|
availableModeControlSize = availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableModes: [CameraMode] = [.photo, .video]
|
var availableModes: [CameraState.CameraMode] = [.photo, .video]
|
||||||
if !isTablet && state.canLivestream {
|
if !isTablet && state.canLivestream {
|
||||||
availableModes.append(.live)
|
availableModes.append(.live)
|
||||||
}
|
}
|
||||||
|
|
@ -2218,6 +2218,12 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
||||||
case avatar
|
case avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum CameraMode {
|
||||||
|
case photo
|
||||||
|
case video
|
||||||
|
case live
|
||||||
|
}
|
||||||
|
|
||||||
public enum PIPPosition: Int32 {
|
public enum PIPPosition: Int32 {
|
||||||
case topLeft
|
case topLeft
|
||||||
case topRight
|
case topRight
|
||||||
|
|
@ -2528,8 +2534,18 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
||||||
self.mainPreviewView.resetPlaceholder(front: cameraFrontPosition)
|
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(
|
self.cameraState = CameraState(
|
||||||
mode: .photo,
|
mode: cameraMode,
|
||||||
position: cameraFrontPosition ? .front : .back,
|
position: cameraFrontPosition ? .front : .back,
|
||||||
flashMode: .off,
|
flashMode: .off,
|
||||||
flashModeDidChange: false,
|
flashModeDidChange: false,
|
||||||
|
|
@ -4002,6 +4018,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
fileprivate let mode: Mode
|
fileprivate let mode: Mode
|
||||||
|
fileprivate let cameraMode: CameraMode
|
||||||
fileprivate let customTarget: EnginePeer.Id?
|
fileprivate let customTarget: EnginePeer.Id?
|
||||||
fileprivate let resumeLiveStream: Bool
|
fileprivate let resumeLiveStream: Bool
|
||||||
fileprivate let holder: CameraHolder?
|
fileprivate let holder: CameraHolder?
|
||||||
|
|
@ -4071,6 +4088,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
cameraMode: CameraMode = .photo,
|
||||||
customTarget: EnginePeer.Id? = nil,
|
customTarget: EnginePeer.Id? = nil,
|
||||||
resumeLiveStream: Bool = false,
|
resumeLiveStream: Bool = false,
|
||||||
holder: CameraHolder? = nil,
|
holder: CameraHolder? = nil,
|
||||||
|
|
@ -4080,6 +4098,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
self.cameraMode = cameraMode
|
||||||
self.customTarget = customTarget
|
self.customTarget = customTarget
|
||||||
self.resumeLiveStream = resumeLiveStream
|
self.resumeLiveStream = resumeLiveStream
|
||||||
self.holder = holder
|
self.holder = holder
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import GlassBackgroundComponent
|
||||||
import LiquidLens
|
import LiquidLens
|
||||||
import TabSelectionRecognizer
|
import TabSelectionRecognizer
|
||||||
|
|
||||||
extension CameraMode {
|
extension CameraState.CameraMode {
|
||||||
func title(strings: PresentationStrings) -> String {
|
func title(strings: PresentationStrings) -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .photo:
|
case .photo:
|
||||||
|
|
@ -28,18 +28,18 @@ final class ModeComponent: Component {
|
||||||
let isTablet: Bool
|
let isTablet: Bool
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let tintColor: UIColor
|
let tintColor: UIColor
|
||||||
let availableModes: [CameraMode]
|
let availableModes: [CameraState.CameraMode]
|
||||||
let currentMode: CameraMode
|
let currentMode: CameraState.CameraMode
|
||||||
let updatedMode: (CameraMode) -> Void
|
let updatedMode: (CameraState.CameraMode) -> Void
|
||||||
let tag: AnyObject?
|
let tag: AnyObject?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
isTablet: Bool,
|
isTablet: Bool,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
tintColor: UIColor,
|
tintColor: UIColor,
|
||||||
availableModes: [CameraMode],
|
availableModes: [CameraState.CameraMode],
|
||||||
currentMode: CameraMode,
|
currentMode: CameraState.CameraMode,
|
||||||
updatedMode: @escaping (CameraMode) -> Void,
|
updatedMode: @escaping (CameraState.CameraMode) -> Void,
|
||||||
tag: AnyObject?
|
tag: AnyObject?
|
||||||
) {
|
) {
|
||||||
self.isTablet = isTablet
|
self.isTablet = isTablet
|
||||||
|
|
|
||||||
|
|
@ -1428,8 +1428,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||||
break
|
break
|
||||||
case .theme:
|
case .theme:
|
||||||
break
|
break
|
||||||
case .settings:
|
|
||||||
break
|
|
||||||
case .premiumOffer:
|
case .premiumOffer:
|
||||||
break
|
break
|
||||||
case .starsTopup:
|
case .starsTopup:
|
||||||
|
|
@ -1465,6 +1463,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||||
break
|
break
|
||||||
case .sendGift:
|
case .sendGift:
|
||||||
break
|
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))
|
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) {
|
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)
|
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) {
|
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 {
|
func update(component: ChatListHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.state = state
|
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]) {
|
public func updateStoryUploadProgress(storyUploadProgress: [EnginePeer.Id: Float]) {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -172,10 +172,10 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
||||||
particleColor: statusParticleColor,
|
particleColor: statusParticleColor,
|
||||||
isVisibleForAnimations: true,
|
isVisibleForAnimations: true,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let strongSelf = self, let titleCredibilityIconView = strongSelf.titleCredibilityIconView else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.openStatusSetup?(titleCredibilityIconView)
|
self.openEmojiStatusSetup()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
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 {
|
public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
let _ = self.updateLayoutInternal(size: availableSize, transition: transition)
|
let _ = self.updateLayoutInternal(size: availableSize, transition: transition)
|
||||||
return availableSize
|
return availableSize
|
||||||
|
|
@ -414,10 +421,10 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
||||||
content: statusContent,
|
content: statusContent,
|
||||||
isVisibleForAnimations: true,
|
isVisibleForAnimations: true,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let strongSelf = self, let titleCredibilityIconView = strongSelf.titleCredibilityIconView else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.openStatusSetup?(titleCredibilityIconView)
|
self.openEmojiStatusSetup()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
|
|
||||||
|
|
@ -167,15 +167,15 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
|
||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
contentHeight += 30.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(
|
let cancelSize = self.cancel.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
GlassBarButtonComponent(
|
GlassBarButtonComponent(
|
||||||
size: barButtonSize,
|
size: barButtonSize,
|
||||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: environment.theme.overallDarkAppearance,
|
isDark: environment.theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
||||||
|
|
||||||
fileprivate let playButtonAnimation = ActionSlot<Void>()
|
fileprivate let playButtonAnimation = ActionSlot<Void>()
|
||||||
private var didPlayAnimation = false
|
private var didPlayAnimation = false
|
||||||
|
|
||||||
|
var cachedDescription: String?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
|
@ -91,16 +93,17 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
|
let background = Child(GradientBackgroundComponent.self)
|
||||||
let closeButton = Child(GlassBarButtonComponent.self)
|
let closeButton = Child(GlassBarButtonComponent.self)
|
||||||
let icon = Child(BundleIconComponent.self)
|
let icon = Child(BundleIconComponent.self)
|
||||||
let title = Child(BalancedTextComponent.self)
|
let logo = Child(BundleIconComponent.self)
|
||||||
let text = Child(BalancedTextComponent.self)
|
let text = Child(BalancedTextComponent.self)
|
||||||
let list = Child(List<Empty>.self)
|
let list = Child(List<Empty>.self)
|
||||||
let additionalText = Child(MultilineTextComponent.self)
|
let additionalText = Child(MultilineTextComponent.self)
|
||||||
let button = Child(ButtonComponent.self)
|
let button = Child(ButtonComponent.self)
|
||||||
|
|
||||||
let navigateDisposable = MetaDisposable()
|
let navigateDisposable = MetaDisposable()
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
let component = context.component
|
let component = context.component
|
||||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
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 sideInset: CGFloat = 30.0 + environment.safeInsets.left
|
||||||
let textSideInset: 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 textFont = Font.regular(15.0)
|
||||||
let boldTextFont = Font.semibold(15.0)
|
let boldTextFont = Font.semibold(15.0)
|
||||||
|
|
||||||
|
|
@ -122,42 +124,48 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
||||||
|
|
||||||
let spacing: CGFloat = 16.0
|
let spacing: CGFloat = 16.0
|
||||||
var contentSize = CGSize(width: context.availableSize.width, height: 28.0)
|
var contentSize = CGSize(width: context.availableSize.width, height: 28.0)
|
||||||
|
|
||||||
let icon = icon.update(
|
let descriptionText: String
|
||||||
component: BundleIconComponent(
|
if let cachedDescription = state.cachedDescription {
|
||||||
name: "Premium/Cocoon", tintColor: nil
|
descriptionText = cachedDescription
|
||||||
),
|
} else {
|
||||||
availableSize: context.availableSize,
|
func updateText(_ input: String) -> String {
|
||||||
transition: context.transition
|
let pattern = #"\(([^()]*)\)"#
|
||||||
)
|
let boldPattern = #"\*\*(.*?)\*\*"#
|
||||||
context.add(icon
|
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + icon.size.height / 2.0))
|
let regex = try! NSRegularExpression(pattern: pattern)
|
||||||
)
|
let boldRegex = try! NSRegularExpression(pattern: boldPattern)
|
||||||
contentSize.height += icon.size.height
|
|
||||||
contentSize.height += 14.0
|
let nsInput = input as NSString
|
||||||
|
var result = input
|
||||||
let title = title.update(
|
|
||||||
component: BalancedTextComponent(
|
let matches = regex.matches(in: input, range: NSRange(location: 0, length: nsInput.length))
|
||||||
text: .plain(NSAttributedString(string: strings.CocoonInfo_Title, font: titleFont, textColor: textColor)),
|
|
||||||
horizontalAlignment: .center,
|
for match in matches.reversed() {
|
||||||
maximumNumberOfLines: 0,
|
let range = match.range(at: 1)
|
||||||
lineSpacing: 0.1
|
let inner = nsInput.substring(with: range)
|
||||||
),
|
|
||||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
let replacedInner = boldRegex.stringByReplacingMatches(
|
||||||
transition: .immediate
|
in: inner,
|
||||||
)
|
range: NSRange(location: 0, length: (inner as NSString).length),
|
||||||
context.add(title
|
withTemplate: "[$1]()"
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
)
|
||||||
)
|
|
||||||
contentSize.height += title.size.height
|
result = (result as NSString).replacingCharacters(in: range, with: replacedInner)
|
||||||
contentSize.height += spacing - 8.0
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
descriptionText = updateText(strings.CocoonInfo_Description)
|
||||||
|
state.cachedDescription = descriptionText
|
||||||
|
}
|
||||||
|
|
||||||
let attributedText = parseMarkdownIntoAttributedString(
|
let attributedText = parseMarkdownIntoAttributedString(
|
||||||
strings.CocoonInfo_Description,
|
descriptionText,
|
||||||
attributes: MarkdownAttributes(
|
attributes: MarkdownAttributes(
|
||||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
body: MarkdownAttributeSet(font: textFont, textColor: UIColor(rgb: 0xb8c9ef)),
|
||||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
bold: MarkdownAttributeSet(font: boldTextFont, textColor: UIColor(rgb: 0xb8c9ef)),
|
||||||
link: MarkdownAttributeSet(font: textFont, textColor: linkColor),
|
link: MarkdownAttributeSet(font: boldTextFont, textColor: UIColor(rgb: 0xffffff)),
|
||||||
linkAttribute: { _ in return nil }
|
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),
|
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||||
transition: .immediate
|
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
|
context.add(text
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
|
||||||
)
|
)
|
||||||
contentSize.height += text.size.height
|
contentSize.height += text.size.height
|
||||||
contentSize.height += spacing + 9.0
|
contentSize.height += spacing + 31.0
|
||||||
|
|
||||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||||
items.append(
|
items.append(
|
||||||
|
|
@ -292,14 +341,14 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
||||||
|
|
||||||
let closeButton = closeButton.update(
|
let closeButton = closeButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: CGSize(width: 40.0, height: 40.0),
|
size: CGSize(width: 44.0, height: 44.0),
|
||||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: UIColor(rgb: 0x071533),
|
||||||
isDark: theme.overallDarkAppearance,
|
isDark: true,
|
||||||
state: .generic,
|
state: .tintedGlass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: "Navigation/Close",
|
name: "Navigation/Close",
|
||||||
tintColor: theme.chat.inputPanel.panelControlColor
|
tintColor: .white
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
action: { [weak state] _ in
|
action: { [weak state] _ in
|
||||||
|
|
@ -309,7 +358,7 @@ private final class CocoonInfoSheetContent: CombinedComponent {
|
||||||
state.dismiss(animated: true)
|
state.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(closeButton
|
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)
|
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(
|
let cancelButtonSize = self.cancelButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(GlassBarButtonComponent(
|
component: AnyComponent(GlassBarButtonComponent(
|
||||||
size: barButtonSize,
|
size: barButtonSize,
|
||||||
backgroundColor: environment.theme.rootController.navigationBar.opaqueBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: environment.theme.overallDarkAppearance,
|
isDark: environment.theme.overallDarkAppearance,
|
||||||
state: .glass,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||||
|
|
|
||||||
|
|
@ -519,13 +519,13 @@ private final class SheetContent: CombinedComponent {
|
||||||
contentSize.height += navigation.size.height
|
contentSize.height += navigation.size.height
|
||||||
|
|
||||||
let isBack = items.count > 1
|
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(
|
let backButton = backButton.update(
|
||||||
component: GlassBarButtonComponent(
|
component: GlassBarButtonComponent(
|
||||||
size: barButtonSize,
|
size: barButtonSize,
|
||||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
backgroundColor: nil,
|
||||||
isDark: environment.theme.overallDarkAppearance,
|
isDark: environment.theme.overallDarkAppearance,
|
||||||
state: .generic,
|
state: .glass,
|
||||||
component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
|
component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
|
||||||
BundleIconComponent(
|
BundleIconComponent(
|
||||||
name: isBack ? "Navigation/Back" : "Navigation/Close",
|
name: isBack ? "Navigation/Back" : "Navigation/Close",
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ struct Particle {
|
||||||
|
|
||||||
kernel void dustEffectInitializeParticle(
|
kernel void dustEffectInitializeParticle(
|
||||||
device Particle *particles [[ buffer(0) ]],
|
device Particle *particles [[ buffer(0) ]],
|
||||||
|
const device float &verticalDirection [[ buffer(1) ]],
|
||||||
uint gid [[ thread_position_in_grid ]]
|
uint gid [[ thread_position_in_grid ]]
|
||||||
) {
|
) {
|
||||||
Loki rng = Loki(gid);
|
Loki rng = Loki(gid);
|
||||||
|
|
@ -49,7 +50,9 @@ kernel void dustEffectInitializeParticle(
|
||||||
|
|
||||||
float direction = rng.rand() * (3.14159265 * 2.0);
|
float direction = rng.rand() * (3.14159265 * 2.0);
|
||||||
float velocity = (0.1 + rng.rand() * (0.2 - 0.1)) * 420.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);
|
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 uint2 &size [[ buffer(1) ]],
|
||||||
const device float &phase [[ buffer(2) ]],
|
const device float &phase [[ buffer(2) ]],
|
||||||
const device float &timeStep [[ buffer(3) ]],
|
const device float &timeStep [[ buffer(3) ]],
|
||||||
|
const device float &verticalDirection [[ buffer(4) ]],
|
||||||
uint gid [[ thread_position_in_grid ]]
|
uint gid [[ thread_position_in_grid ]]
|
||||||
) {
|
) {
|
||||||
uint count = size.x * size.y;
|
uint count = size.x * size.y;
|
||||||
|
|
@ -124,7 +128,7 @@ kernel void dustEffectUpdateParticle(
|
||||||
Particle particle = particles[gid];
|
Particle particle = particles[gid];
|
||||||
particle.offsetFromBasePosition += (particle.velocity * timeStep) * particleFraction;
|
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);
|
particle.lifetime = max(0.0, particle.lifetime - timeStep * particleFraction);
|
||||||
particles[gid] = particle;
|
particles[gid] = particle;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
||||||
|
|
||||||
public var animationSpeed: Float = 1.0
|
public var animationSpeed: Float = 1.0
|
||||||
public var playsBackwards: Bool = false
|
public var playsBackwards: Bool = false
|
||||||
|
public var animateDown: Bool = false
|
||||||
|
|
||||||
public var becameEmpty: (() -> Void)?
|
public var becameEmpty: (() -> Void)?
|
||||||
|
|
||||||
|
|
@ -280,6 +281,8 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var verticalDirection: Float = self.animateDown ? -1.0 : 1.0
|
||||||
|
|
||||||
for item in self.items {
|
for item in self.items {
|
||||||
guard let particleBuffer = item.particleBuffer else {
|
guard let particleBuffer = item.particleBuffer else {
|
||||||
continue
|
continue
|
||||||
|
|
@ -297,6 +300,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
||||||
if !item.particleBufferIsInitialized {
|
if !item.particleBufferIsInitialized {
|
||||||
item.particleBufferIsInitialized = true
|
item.particleBufferIsInitialized = true
|
||||||
computeEncoder.setComputePipelineState(state.computePipelineStateInitializeParticle)
|
computeEncoder.setComputePipelineState(state.computePipelineStateInitializeParticle)
|
||||||
|
computeEncoder.setBytes(&verticalDirection, length: 4, index: 1)
|
||||||
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,6 +313,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
|
||||||
var timeStep: Float = Float(lastTimeStep) / Float(UIView.animationDurationFactor())
|
var timeStep: Float = Float(lastTimeStep) / Float(UIView.animationDurationFactor())
|
||||||
timeStep *= 2.0
|
timeStep *= 2.0
|
||||||
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
|
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
|
||||||
|
computeEncoder.setBytes(&verticalDirection, length: 4, index: 4)
|
||||||
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ swift_library(
|
||||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/Components/ResizableSheetComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//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