mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '437cd445d16a8a02c3c34b7a7a98266325b780ea'
This commit is contained in:
commit
1987557d66
@ -6507,9 +6507,16 @@ Sorry for the inconvenience.";
|
||||
"ImportStickerPack.RemoveFromImport" = "Remove From Import";
|
||||
"ImportStickerPack.ChooseName" = "Choose Name";
|
||||
"ImportStickerPack.ChooseNameDescription" = "Please choose a name for your set.";
|
||||
"ImportStickerPack.GeneratingLink" = "generating link...";
|
||||
"ImportStickerPack.CheckingLink" = "checking availability...";
|
||||
"ImportStickerPack.ChooseLink" = "Choose Link";
|
||||
"ImportStickerPack.ChooseLinkDescription" = "You can use a-z, 0-9 and underscores.";
|
||||
"ImportStickerPack.LinkTaken" = "Sorry, this link is alreay taken.";
|
||||
"ImportStickerPack.LinkAvailable" = "Link is available.";
|
||||
"ImportStickerPack.ImportingStickers" = "Importing Stickers";
|
||||
"ImportStickerPack.Of" = "%1$@ of %2$@ Imported";
|
||||
"ImportStickerPack.InProgress" = "Please keep this window open\nuntil the import is completed.";
|
||||
"ImportStickerPack.Create" = "Create";
|
||||
|
||||
"WallpaperPreview.PreviewBottomTextAnimatable" = "Tap the play button to view the background animation.";
|
||||
|
||||
|
@ -19,6 +19,7 @@ swift_library(
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -13,6 +13,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
|
||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
@ -27,18 +28,21 @@ private final class DebugControllerArguments {
|
||||
let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
|
||||
let pushController: (ViewController) -> Void
|
||||
let getRootController: () -> UIViewController?
|
||||
let getNavigationController: () -> NavigationController?
|
||||
|
||||
init(sharedContext: SharedAccountContext, context: AccountContext?, mailComposeDelegate: DebugControllerMailComposeDelegate, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, getRootController: @escaping () -> UIViewController?) {
|
||||
init(sharedContext: SharedAccountContext, context: AccountContext?, mailComposeDelegate: DebugControllerMailComposeDelegate, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, getRootController: @escaping () -> UIViewController?, getNavigationController: @escaping () -> NavigationController?) {
|
||||
self.sharedContext = sharedContext
|
||||
self.context = context
|
||||
self.mailComposeDelegate = mailComposeDelegate
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.getRootController = getRootController
|
||||
self.getNavigationController = getNavigationController
|
||||
}
|
||||
}
|
||||
|
||||
private enum DebugControllerSection: Int32 {
|
||||
case sticker
|
||||
case logs
|
||||
case logging
|
||||
case experiments
|
||||
@ -48,6 +52,7 @@ private enum DebugControllerSection: Int32 {
|
||||
}
|
||||
|
||||
private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case testStickerImport(PresentationTheme)
|
||||
case sendLogs(PresentationTheme)
|
||||
case sendOneLog(PresentationTheme)
|
||||
case sendShareLogs
|
||||
@ -86,6 +91,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .testStickerImport:
|
||||
return DebugControllerSection.sticker.rawValue
|
||||
case .sendLogs, .sendOneLog, .sendShareLogs, .sendNotificationLogs, .sendCriticalLogs:
|
||||
return DebugControllerSection.logs.rawValue
|
||||
case .accounts:
|
||||
@ -107,68 +114,70 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int {
|
||||
switch self {
|
||||
case .sendLogs:
|
||||
case .testStickerImport:
|
||||
return 0
|
||||
case .sendOneLog:
|
||||
case .sendLogs:
|
||||
return 1
|
||||
case .sendShareLogs:
|
||||
case .sendOneLog:
|
||||
return 2
|
||||
case .sendNotificationLogs:
|
||||
case .sendShareLogs:
|
||||
return 3
|
||||
case .sendCriticalLogs:
|
||||
case .sendNotificationLogs:
|
||||
return 4
|
||||
case .accounts:
|
||||
case .sendCriticalLogs:
|
||||
return 5
|
||||
case .logToFile:
|
||||
case .accounts:
|
||||
return 6
|
||||
case .logToConsole:
|
||||
case .logToFile:
|
||||
return 7
|
||||
case .redactSensitiveData:
|
||||
case .logToConsole:
|
||||
return 8
|
||||
case .enableRaiseToSpeak:
|
||||
case .redactSensitiveData:
|
||||
return 9
|
||||
case .keepChatNavigationStack:
|
||||
case .enableRaiseToSpeak:
|
||||
return 10
|
||||
case .skipReadHistory:
|
||||
case .keepChatNavigationStack:
|
||||
return 11
|
||||
case .crashOnSlowQueries:
|
||||
case .skipReadHistory:
|
||||
return 12
|
||||
case .clearTips:
|
||||
case .crashOnSlowQueries:
|
||||
return 13
|
||||
case .crash:
|
||||
case .clearTips:
|
||||
return 14
|
||||
case .resetData:
|
||||
case .crash:
|
||||
return 15
|
||||
case .resetDatabase:
|
||||
case .resetData:
|
||||
return 16
|
||||
case .resetDatabaseAndCache:
|
||||
case .resetDatabase:
|
||||
return 17
|
||||
case .resetHoles:
|
||||
case .resetDatabaseAndCache:
|
||||
return 18
|
||||
case .reindexUnread:
|
||||
case .resetHoles:
|
||||
return 19
|
||||
case .resetBiometricsData:
|
||||
case .reindexUnread:
|
||||
return 20
|
||||
case .optimizeDatabase:
|
||||
case .resetBiometricsData:
|
||||
return 21
|
||||
case .photoPreview:
|
||||
case .optimizeDatabase:
|
||||
return 22
|
||||
case .knockoutWallpaper:
|
||||
case .photoPreview:
|
||||
return 23
|
||||
case .demoVideoChats:
|
||||
case .knockoutWallpaper:
|
||||
return 24
|
||||
case .experimentalCompatibility:
|
||||
case .demoVideoChats:
|
||||
return 25
|
||||
case .enableNoiseSuppression:
|
||||
case .experimentalCompatibility:
|
||||
return 26
|
||||
case .playerEmbedding:
|
||||
case .enableNoiseSuppression:
|
||||
return 27
|
||||
case .playlistPlayback:
|
||||
case .playerEmbedding:
|
||||
return 28
|
||||
case .voiceConference:
|
||||
case .playlistPlayback:
|
||||
return 29
|
||||
case .voiceConference:
|
||||
return 30
|
||||
case let .preferredVideoCodec(index, _, _, _):
|
||||
return 30 + index
|
||||
return 31 + index
|
||||
case .disableVideoAspectScaling:
|
||||
return 100
|
||||
case .enableVoipTcp:
|
||||
@ -187,7 +196,22 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! DebugControllerArguments
|
||||
switch self {
|
||||
case let .sendLogs(theme):
|
||||
case .testStickerImport:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Simulate Stickers Import", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
if let url = getAppBundle().url(forResource: "importstickers", withExtension: "json"), let data = try? Data(contentsOf: url) {
|
||||
let dataType = "org.telegram.third-party.stickerset"
|
||||
if #available(iOS 10.0, *) {
|
||||
UIPasteboard.general.setItems([[dataType: data]], options: [UIPasteboard.OptionsKey.localOnly: true, UIPasteboard.OptionsKey.expirationDate: NSDate(timeIntervalSinceNow: 60)])
|
||||
} else {
|
||||
UIPasteboard.general.setData(data, forPasteboardType: dataType)
|
||||
}
|
||||
context.sharedContext.openResolvedUrl(.importStickers, context: context, urlContext: .generic, navigationController: arguments.getNavigationController(), openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in arguments.presentController(c, a as? ViewControllerPresentationArguments) }, dismissInput: {}, contentContext: nil)
|
||||
}
|
||||
})
|
||||
case .sendLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectLogs()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -257,7 +281,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .sendOneLog(theme):
|
||||
case .sendOneLog:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Latest Logs (Up to 4 MB)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectLogs()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -339,7 +363,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .sendShareLogs:
|
||||
case .sendShareLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectLogs(prefix: "/share-logs")
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -409,7 +433,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .sendNotificationLogs(theme):
|
||||
case .sendNotificationLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger(rootPath: arguments.sharedContext.basePath, basePath: arguments.sharedContext.basePath + "/notificationServiceLogs").collectLogs()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -434,7 +458,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.pushController(controller)
|
||||
})
|
||||
})
|
||||
case let .sendCriticalLogs(theme):
|
||||
case .sendCriticalLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectShortLogFiles()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -487,38 +511,38 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .accounts(theme):
|
||||
case .accounts:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
arguments.pushController(debugAccountsController(context: context, accountManager: arguments.sharedContext.accountManager))
|
||||
})
|
||||
case let .logToFile(theme, value):
|
||||
case let .logToFile(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Log to File", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedLogToFile(value)
|
||||
}).start()
|
||||
})
|
||||
case let .logToConsole(theme, value):
|
||||
case let .logToConsole(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Log to Console", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedLogToConsole(value)
|
||||
}).start()
|
||||
})
|
||||
case let .redactSensitiveData(theme, value):
|
||||
case let .redactSensitiveData(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Remove Sensitive Data", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedRedactSensitiveData(value)
|
||||
}).start()
|
||||
})
|
||||
case let .enableRaiseToSpeak(theme, value):
|
||||
case let .enableRaiseToSpeak(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Enable Raise to Speak", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateMediaInputSettingsInteractively(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedEnableRaiseToSpeak(value)
|
||||
}).start()
|
||||
})
|
||||
case let .keepChatNavigationStack(theme, value):
|
||||
case let .keepChatNavigationStack(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Keep Chat Stack", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
@ -526,7 +550,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .skipReadHistory(theme, value):
|
||||
case let .skipReadHistory(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Skip read history", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
@ -534,7 +558,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .crashOnSlowQueries(theme, value):
|
||||
case let .crashOnSlowQueries(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
@ -542,7 +566,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .clearTips(theme):
|
||||
case .clearTips:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.clearNotices()
|
||||
@ -556,11 +580,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
case let .crash(theme):
|
||||
case .crash:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
preconditionFailure()
|
||||
})
|
||||
case let .resetData(theme):
|
||||
case .resetData:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
@ -579,7 +603,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetDatabase(theme):
|
||||
case .resetDatabase:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Clear Database", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -602,7 +626,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetDatabaseAndCache(theme):
|
||||
case .resetDatabaseAndCache:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Clear Database and Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -625,7 +649,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetHoles(theme):
|
||||
case .resetHoles:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -640,7 +664,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
controller.dismiss()
|
||||
})
|
||||
})
|
||||
case let .reindexUnread(theme):
|
||||
case .reindexUnread:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reindex Unread Counters", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -655,13 +679,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
controller.dismiss()
|
||||
})
|
||||
})
|
||||
case let .resetBiometricsData(theme):
|
||||
case .resetBiometricsData:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Biometrics Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
let _ = updatePresentationPasscodeSettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil)
|
||||
}).start()
|
||||
})
|
||||
case let .optimizeDatabase(theme):
|
||||
case .optimizeDatabase:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -677,7 +701,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(controller, nil)
|
||||
})
|
||||
})
|
||||
case let .photoPreview(theme, value):
|
||||
case let .photoPreview(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Media Preview (Updated)", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
@ -783,9 +807,9 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .hostInfo(theme, string):
|
||||
case let .hostInfo(_, string):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
|
||||
case let .versionInfo(theme):
|
||||
case .versionInfo:
|
||||
let bundle = Bundle.main
|
||||
let bundleId = bundle.bundleIdentifier ?? ""
|
||||
let bundleVersion = bundle.infoDictionary?["CFBundleShortVersionString"] ?? ""
|
||||
@ -800,6 +824,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
||||
|
||||
let isMainApp = sharedContext.applicationBindings.isMainApp
|
||||
|
||||
// entries.append(.testStickerImport(presentationData.theme))
|
||||
entries.append(.sendLogs(presentationData.theme))
|
||||
entries.append(.sendOneLog(presentationData.theme))
|
||||
entries.append(.sendShareLogs)
|
||||
@ -872,6 +897,7 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var getRootControllerImpl: (() -> UIViewController?)?
|
||||
var getNavigationControllerImpl: (() -> NavigationController?)?
|
||||
|
||||
let arguments = DebugControllerArguments(sharedContext: sharedContext, context: context, mailComposeDelegate: DebugControllerMailComposeDelegate(), presentController: { controller, arguments in
|
||||
presentControllerImpl?(controller, arguments)
|
||||
@ -879,6 +905,8 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
|
||||
pushControllerImpl?(controller)
|
||||
}, getRootController: {
|
||||
return getRootControllerImpl?()
|
||||
}, getNavigationController: {
|
||||
return getNavigationControllerImpl?()
|
||||
})
|
||||
|
||||
let appGroupName = "group.\(Bundle.main.bundleIdentifier!)"
|
||||
@ -945,5 +973,8 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
|
||||
getRootControllerImpl = { [weak controller] in
|
||||
return controller?.view.window?.rootViewController
|
||||
}
|
||||
getNavigationControllerImpl = { [weak controller] in
|
||||
return controller?.navigationController as? NavigationController
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -86,11 +86,15 @@ open class AlertController: ViewController, StandalonePresentableController {
|
||||
private let contentNode: AlertContentNode
|
||||
private let allowInputInset: Bool
|
||||
|
||||
private weak var existingAlertController: AlertController?
|
||||
|
||||
public var willDismiss: (() -> Void)?
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
public init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool = true) {
|
||||
public init(theme: AlertControllerTheme, contentNode: AlertContentNode, existingAlertController: AlertController? = nil, allowInputInset: Bool = true) {
|
||||
self.theme = theme
|
||||
self.contentNode = contentNode
|
||||
self.existingAlertController = existingAlertController
|
||||
self.allowInputInset = allowInputInset
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
@ -108,8 +112,11 @@ open class AlertController: ViewController, StandalonePresentableController {
|
||||
self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme, allowInputInset: self.allowInputInset)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.controllerNode.existingAlertControllerNode = self.existingAlertController?.controllerNode
|
||||
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
if let strongSelf = self, strongSelf.contentNode.dismissOnOutsideTap {
|
||||
strongSelf.willDismiss?()
|
||||
strongSelf.controllerNode.animateOut {
|
||||
self?.dismiss()
|
||||
}
|
||||
@ -120,6 +127,9 @@ open class AlertController: ViewController, StandalonePresentableController {
|
||||
override open func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.existingAlertController?.dismiss(completion: nil)
|
||||
self.existingAlertController = nil
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class AlertControllerNode: ASDisplayNode {
|
||||
var existingAlertControllerNode: AlertControllerNode?
|
||||
|
||||
private let centerDimView: UIImageView
|
||||
private let topDimView: UIView
|
||||
private let bottomDimView: UIView
|
||||
@ -90,18 +92,35 @@ final class AlertControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.centerDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.topDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.leftDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.rightDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] finished in
|
||||
if finished {
|
||||
self?.centerDimView.backgroundColor = nil
|
||||
self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
if let previousNode = self.existingAlertControllerNode {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
||||
|
||||
previousNode.position = previousNode.position.offsetBy(dx: -previousNode.frame.width, dy: 0.0)
|
||||
self.addSubnode(previousNode)
|
||||
|
||||
let position = self.position
|
||||
self.position = position.offsetBy(dx: self.frame.width, dy: 0.0)
|
||||
transition.animateView {
|
||||
self.position = position
|
||||
} completion: { _ in
|
||||
previousNode.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
|
||||
|
||||
self.existingAlertControllerNode = nil
|
||||
} else {
|
||||
self.centerDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.topDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.leftDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.rightDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] finished in
|
||||
if finished {
|
||||
self?.centerDimView.backgroundColor = nil
|
||||
self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
}
|
||||
})
|
||||
self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Postbox
|
||||
|
||||
public class ImportStickerPack {
|
||||
public class Sticker: Equatable {
|
||||
@ -15,6 +16,7 @@ public class ImportStickerPack {
|
||||
let content: Content
|
||||
let emojis: [String]
|
||||
let uuid: UUID
|
||||
var resource: MediaResource?
|
||||
|
||||
init(content: Content, emojis: [String], uuid: UUID = UUID()) {
|
||||
self.content = content
|
||||
|
@ -73,6 +73,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
private let radialStatusBackground: ASImageNode
|
||||
private let radialStatusText: ImmediateTextNode
|
||||
private let progressText: ImmediateTextNode
|
||||
private let infoText: ImmediateTextNode
|
||||
|
||||
private var interaction: StickerPackPreviewInteraction!
|
||||
|
||||
@ -90,6 +91,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private let shortNameSuggestionDisposable = MetaDisposable()
|
||||
|
||||
private var progress: (CGFloat, Int32, Int32)?
|
||||
|
||||
@ -134,23 +136,37 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
self.installActionSeparatorNode.displaysAsynchronously = false
|
||||
|
||||
self.radialStatus = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
self.radialStatus.alpha = 0.0
|
||||
self.radialCheck = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
self.radialCheck.alpha = 0.0
|
||||
|
||||
self.radialStatusBackground = ASImageNode()
|
||||
self.radialStatusBackground.isUserInteractionEnabled = false
|
||||
self.radialStatusBackground.displaysAsynchronously = false
|
||||
self.radialStatusBackground.image = generateCircleImage(diameter: 180.0, lineWidth: 6.0, color: self.presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.2))
|
||||
self.radialStatusBackground.alpha = 0.0
|
||||
|
||||
self.radialStatusText = ImmediateTextNode()
|
||||
self.radialStatusText.isUserInteractionEnabled = false
|
||||
self.radialStatusText.displaysAsynchronously = false
|
||||
self.radialStatusText.maximumNumberOfLines = 1
|
||||
self.radialStatusText.isAccessibilityElement = false
|
||||
self.radialStatusText.alpha = 0.0
|
||||
|
||||
self.progressText = ImmediateTextNode()
|
||||
self.progressText.isUserInteractionEnabled = false
|
||||
self.progressText.displaysAsynchronously = false
|
||||
self.progressText.maximumNumberOfLines = 1
|
||||
self.progressText.isAccessibilityElement = false
|
||||
self.progressText.alpha = 0.0
|
||||
|
||||
self.infoText = ImmediateTextNode()
|
||||
self.infoText.isUserInteractionEnabled = false
|
||||
self.infoText.displaysAsynchronously = false
|
||||
self.infoText.maximumNumberOfLines = 4
|
||||
self.infoText.isAccessibilityElement = false
|
||||
self.infoText.textAlignment = .center
|
||||
self.infoText.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
@ -184,6 +200,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
self.wrappingScrollNode.addSubnode(self.radialCheck)
|
||||
self.wrappingScrollNode.addSubnode(self.radialStatusText)
|
||||
self.wrappingScrollNode.addSubnode(self.progressText)
|
||||
self.wrappingScrollNode.addSubnode(self.infoText)
|
||||
|
||||
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
||||
@ -192,6 +209,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.shortNameSuggestionDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -254,18 +272,6 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
self.presentationData = presentationData
|
||||
|
||||
let theme = presentationData.theme
|
||||
let solidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let highlightedSolidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
@ -337,7 +343,6 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
|
||||
var transaction: StickerPackPreviewGridTransaction?
|
||||
|
||||
var itemCount = 0
|
||||
var animateIn = false
|
||||
|
||||
var forceTitleUpdate = false
|
||||
@ -359,12 +364,12 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
}
|
||||
self.contentTitleNode.attributedText = stringWithAppliedEntities(title, 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)
|
||||
animateIn = true
|
||||
itemCount = self.currentItems.count
|
||||
|
||||
if !forceTitleUpdate {
|
||||
transaction = StickerPackPreviewGridTransaction(previousList: previousItems, list: self.currentItems, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme)
|
||||
}
|
||||
}
|
||||
let itemCount = self.currentItems.count
|
||||
|
||||
let titleSize = self.contentTitleNode.updateLayout(CGSize(width: contentContainerFrame.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: contentContainerFrame.minX + floor((contentContainerFrame.size.width - titleSize.width) / 2.0), y: self.contentBackgroundNode.frame.minY + 15.0), size: titleSize)
|
||||
@ -406,63 +411,80 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
|
||||
transition.updateAlpha(node: self.contentGridNode, alpha: self.progress == nil ? 1.0 : 0.0)
|
||||
|
||||
if let (progress, count, total) = self.progress {
|
||||
let effectiveProgress = progress
|
||||
|
||||
let availableHeight: CGFloat = 330.0
|
||||
var radialStatusSize = CGSize(width: 186.0, height: 186.0)
|
||||
var maxIconStatusSpacing: CGFloat = 46.0
|
||||
var maxProgressTextSpacing: CGFloat = 33.0
|
||||
var progressStatusSpacing: CGFloat = 14.0
|
||||
var statusButtonSpacing: CGFloat = 19.0
|
||||
|
||||
var maxK: CGFloat = availableHeight / (30.0 + maxProgressTextSpacing + 320.0)
|
||||
maxK = max(0.5, min(1.0, maxK))
|
||||
|
||||
radialStatusSize.width = floor(radialStatusSize.width * maxK)
|
||||
radialStatusSize.height = floor(radialStatusSize.height * maxK)
|
||||
maxIconStatusSpacing = floor(maxIconStatusSpacing * maxK)
|
||||
maxProgressTextSpacing = floor(maxProgressTextSpacing * maxK)
|
||||
progressStatusSpacing = floor(progressStatusSpacing * maxK)
|
||||
statusButtonSpacing = floor(statusButtonSpacing * maxK)
|
||||
|
||||
var updateRadialBackround = false
|
||||
if let width = self.radialStatusBackground.image?.size.width {
|
||||
if abs(width - radialStatusSize.width) > 0.01 {
|
||||
updateRadialBackround = true
|
||||
}
|
||||
} else {
|
||||
var effectiveProgress: CGFloat = 0.0
|
||||
var count: Int32 = 0
|
||||
var total: Int32 = 0
|
||||
|
||||
var hasProgress = false
|
||||
if let (progress, progressCount, progressTotal) = self.progress {
|
||||
effectiveProgress = progress
|
||||
count = progressCount
|
||||
total = progressTotal
|
||||
hasProgress = true
|
||||
}
|
||||
|
||||
let availableHeight: CGFloat = 330.0
|
||||
var radialStatusSize = CGSize(width: 186.0, height: 186.0)
|
||||
var maxIconStatusSpacing: CGFloat = 46.0
|
||||
var maxProgressTextSpacing: CGFloat = 33.0
|
||||
var progressStatusSpacing: CGFloat = 14.0
|
||||
var statusButtonSpacing: CGFloat = 19.0
|
||||
|
||||
var maxK: CGFloat = availableHeight / (30.0 + maxProgressTextSpacing + 320.0)
|
||||
maxK = max(0.5, min(1.0, maxK))
|
||||
|
||||
radialStatusSize.width = floor(radialStatusSize.width * maxK)
|
||||
radialStatusSize.height = floor(radialStatusSize.height * maxK)
|
||||
maxIconStatusSpacing = floor(maxIconStatusSpacing * maxK)
|
||||
maxProgressTextSpacing = floor(maxProgressTextSpacing * maxK)
|
||||
progressStatusSpacing = floor(progressStatusSpacing * maxK)
|
||||
statusButtonSpacing = floor(statusButtonSpacing * maxK)
|
||||
|
||||
var updateRadialBackround = false
|
||||
if let width = self.radialStatusBackground.image?.size.width {
|
||||
if abs(width - radialStatusSize.width) > 0.01 {
|
||||
updateRadialBackround = true
|
||||
}
|
||||
|
||||
if updateRadialBackround {
|
||||
self.radialStatusBackground.image = generateCircleImage(diameter: radialStatusSize.width, lineWidth: 6.0, color: self.presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.2))
|
||||
}
|
||||
|
||||
let contentOrigin = self.contentBackgroundNode.frame.minY + 72.0
|
||||
} else {
|
||||
updateRadialBackround = true
|
||||
}
|
||||
|
||||
if updateRadialBackround {
|
||||
self.radialStatusBackground.image = generateCircleImage(diameter: radialStatusSize.width, lineWidth: 6.0, color: self.presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.2))
|
||||
}
|
||||
|
||||
let contentOrigin = self.contentBackgroundNode.frame.minY + 72.0
|
||||
if hasProgress {
|
||||
transition.updateAlpha(node: self.radialStatusText, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.progressText, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.radialStatus, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.infoText, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.radialCheck, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.radialStatusBackground, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.installActionButtonNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.contentSeparatorNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.installActionSeparatorNode, alpha: 0.0)
|
||||
|
||||
self.radialStatusText.attributedText = NSAttributedString(string: "\(Int(effectiveProgress * 100.0))%", font: Font.with(size: floor(36.0 * maxK), design: .round, weight: .semibold), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let radialStatusTextSize = self.radialStatusText.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
self.progressText.attributedText = NSAttributedString(string: self.presentationData.strings.ImportStickerPack_Of(String(count), String(total)).0, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let progressTextSize = self.progressText.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
self.radialStatus.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - radialStatusSize.width) / 2.0), y: contentOrigin), size: radialStatusSize)
|
||||
let checkSize: CGFloat = 130.0
|
||||
self.radialCheck.frame = CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - checkSize) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))
|
||||
self.radialStatusBackground.frame = self.radialStatus.frame
|
||||
|
||||
self.radialStatusText.frame = CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - radialStatusTextSize.width) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - radialStatusTextSize.height) / 2.0)), size: radialStatusTextSize)
|
||||
|
||||
self.progressText.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - progressTextSize.width) / 2.0), y: (self.radialStatus.frame.maxY + maxProgressTextSpacing)), size: progressTextSize)
|
||||
}
|
||||
|
||||
self.radialStatusText.attributedText = NSAttributedString(string: "\(Int(effectiveProgress * 100.0))%", font: Font.with(size: floor(36.0 * maxK), design: .round, weight: .semibold), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let radialStatusTextSize = self.radialStatusText.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
self.progressText.attributedText = NSAttributedString(string: self.presentationData.strings.ImportStickerPack_Of(String(count), String(total)).0, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let progressTextSize = self.progressText.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
self.infoText.attributedText = NSAttributedString(string: self.presentationData.strings.ImportStickerPack_InProgress, font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor)
|
||||
let infoTextSize = self.infoText.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
transition.updateFrame(node: self.radialStatus, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - radialStatusSize.width) / 2.0), y: contentOrigin), size: radialStatusSize))
|
||||
let checkSize: CGFloat = 130.0
|
||||
transition.updateFrame(node: self.radialCheck, frame: CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - checkSize) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
|
||||
transition.updateFrame(node: self.radialStatusBackground, frame: self.radialStatus.frame)
|
||||
|
||||
transition.updateFrame(node: self.radialStatusText, frame: CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - radialStatusTextSize.width) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - radialStatusTextSize.height) / 2.0)), size: radialStatusTextSize))
|
||||
|
||||
transition.updateFrame(node: self.progressText, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - progressTextSize.width) / 2.0), y: (self.radialStatus.frame.maxY + maxProgressTextSpacing)), size: progressTextSize))
|
||||
|
||||
transition.updateFrame(node: self.infoText, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - infoTextSize.width) / 2.0), y: (self.progressText.frame.maxY + maxProgressTextSpacing) + 10.0), size: infoTextSize))
|
||||
}
|
||||
|
||||
private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) {
|
||||
@ -498,7 +520,6 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
backgroundFrame.origin.y -= buttonHeight + 32.0 - backgroundFrame.size.height
|
||||
backgroundFrame.size.height = buttonHeight + 32.0
|
||||
}
|
||||
var compactFrame = false
|
||||
let backgroundDeltaY = backgroundFrame.minY - self.contentBackgroundNode.frame.minY
|
||||
transition.updateFrame(node: self.contentBackgroundNode, frame: backgroundFrame)
|
||||
transition.animatePositionAdditive(node: self.contentGridNode, offset: CGPoint(x: 0.0, y: -backgroundDeltaY))
|
||||
@ -509,7 +530,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
|
||||
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: backgroundFrame.minY + titleAreaHeight), size: CGSize(width: contentFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
if !compactFrame && CGFloat(0.0).isLessThanOrEqualTo(presentationLayout.contentOffset.y) {
|
||||
if CGFloat(0.0).isLessThanOrEqualTo(presentationLayout.contentOffset.y) {
|
||||
self.contentSeparatorNode.alpha = 1.0
|
||||
} else {
|
||||
self.contentSeparatorNode.alpha = 0.0
|
||||
@ -524,6 +545,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
}
|
||||
|
||||
@objc func cancelButtonPressed() {
|
||||
self.disposable.set(nil)
|
||||
self.cancel?()
|
||||
}
|
||||
|
||||
@ -538,9 +560,9 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
if case let .image(data) = item.stickerItem.content, let image = UIImage(data: data) {
|
||||
dimensions = PixelDimensions(image.size)
|
||||
}
|
||||
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
||||
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.stickerItem.data)
|
||||
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions))
|
||||
if let resource = item.stickerItem.resource {
|
||||
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions))
|
||||
}
|
||||
}
|
||||
var thumbnailSticker: ImportSticker?
|
||||
if let thumbnail = stickerPack.thumbnail {
|
||||
@ -553,6 +575,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
thumbnailSticker = ImportSticker(resource: resource, emojis: [], dimensions: dimensions)
|
||||
}
|
||||
|
||||
let firstStickerItem = thumbnailSticker ?? stickers.first
|
||||
|
||||
self.progress = (0.0, 0, Int32(stickers.count))
|
||||
self.radialStatus.transitionToState(.progress(color: self.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: max(0.01, 0.0), cancelEnabled: false, animateRotation: false), animated: false, synchronous: true, completion: {})
|
||||
if let (layout, navigationBarHeight) = self.containerLayout {
|
||||
@ -597,8 +621,12 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
strongSelf.cancelButtonNode.isUserInteractionEnabled = false
|
||||
|
||||
Queue.mainQueue().after(1.0) {
|
||||
strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: items.first, context: strongSelf.context), elevatedLayout: false, action: { _ in return true}), nil)
|
||||
strongSelf.dismiss?()
|
||||
var firstItem: StickerPackItem?
|
||||
if let firstStickerItem = firstStickerItem, let resource = firstStickerItem.resource as? TelegramMediaResource {
|
||||
firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: stickerPack.isAnimated ? "application/x-tgsticker": "image/png", size: nil, attributes: [.FileName(fileName: stickerPack.isAnimated ? "sticker.tgs" : "sticker.png"), .ImageSize(size: firstStickerItem.dimensions)]), indexKeys: [])
|
||||
}
|
||||
strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { _ in return true}), nil)
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
} else if case let .progress(progress, count, total) = status {
|
||||
strongSelf.progress = (CGFloat(progress), count, total)
|
||||
@ -616,14 +644,31 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
}
|
||||
|
||||
@objc func installActionButtonPressed() {
|
||||
let controller = importStickerPackTitleController(sharedContext: self.context.sharedContext, account: self.context.account, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: "", doneButtonTitle: nil, value: nil, maxLength: 128, apply: { [weak self] title in
|
||||
if let strongSelf = self, let stickerPack = strongSelf.stickerPack, var title = title {
|
||||
title = title.trimmingTrailingSpaces()
|
||||
|
||||
|
||||
var proceedImpl: ((String, String?) -> Void)?
|
||||
let titleController = importStickerPackTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: "", value: nil, maxLength: 128, apply: { [weak self] title in
|
||||
if let strongSelf = self, let title = title {
|
||||
strongSelf.shortNameSuggestionDisposable.set((strongSelf.context.engine.stickers.getStickerSetShortNameSuggestion(title: title)
|
||||
|> deliverOnMainQueue).start(next: { suggestedShortName in
|
||||
proceedImpl?(title, suggestedShortName)
|
||||
}))
|
||||
}
|
||||
}, cancel: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.shortNameSuggestionDisposable.set(nil)
|
||||
}
|
||||
})
|
||||
self.present?(controller, nil)
|
||||
proceedImpl = { [weak titleController, weak self] title, suggestedShortName in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let controller = importStickerPackShortNameController(context: strongSelf.context, title: strongSelf.presentationData.strings.ImportStickerPack_ChooseLink, text: strongSelf.presentationData.strings.ImportStickerPack_ChooseLinkDescription, placeholder: "", value: suggestedShortName, maxLength: 60, existingAlertController: titleController, apply: { [weak self] shortName in
|
||||
if let shortName = shortName {
|
||||
self?.createStickerSet(title: title, shortName: shortName)
|
||||
}
|
||||
})
|
||||
strongSelf.present?(controller, nil)
|
||||
}
|
||||
self.present?(titleController, nil)
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
@ -665,49 +710,19 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
self.stickerPack = stickerPack
|
||||
var updatedItems: [StickerPackPreviewGridEntry] = []
|
||||
for item in stickerPack.stickers {
|
||||
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
||||
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.data)
|
||||
item.resource = resource
|
||||
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
|
||||
}
|
||||
self.pendingItems = updatedItems
|
||||
|
||||
// self.interaction.playAnimatedStickers = stickerSettings.loopAnimatedStickers
|
||||
self.interaction.playAnimatedStickers = true
|
||||
|
||||
if let _ = self.containerLayout {
|
||||
self.dequeueUpdateStickerPack()
|
||||
}
|
||||
self.installActionButtonNode.setTitle(self.presentationData.strings.ImportStickerPack_CreateStickerSet, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
// switch stickerPack {
|
||||
// case .none, .fetching:
|
||||
// self.installActionSeparatorNode.alpha = 0.0
|
||||
// self.shareActionSeparatorNode.alpha = 0.0
|
||||
// self.shareActionButtonNode.alpha = 0.0
|
||||
// self.installActionButtonNode.alpha = 0.0
|
||||
// self.installActionButtonNode.setTitle("", with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
|
||||
// case let .result(info, _, installed):
|
||||
// if self.stickerPackInitiallyInstalled == nil {
|
||||
// self.stickerPackInitiallyInstalled = installed
|
||||
// }
|
||||
// self.installActionSeparatorNode.alpha = 1.0
|
||||
// self.shareActionSeparatorNode.alpha = 1.0
|
||||
// self.shareActionButtonNode.alpha = 1.0
|
||||
// self.installActionButtonNode.alpha = 1.0
|
||||
// if installed {
|
||||
// let text: String
|
||||
// if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
// text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count)
|
||||
// } else {
|
||||
// text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count)
|
||||
// }
|
||||
// self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal)
|
||||
// } else {
|
||||
// let text: String
|
||||
// if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
// text = self.presentationData.strings.StickerPack_AddStickerCount(info.count)
|
||||
// } else {
|
||||
// text = self.presentationData.strings.StickerPack_AddMaskCount(info.count)
|
||||
// }
|
||||
// self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func dequeueUpdateStickerPack() {
|
||||
@ -717,6 +732,10 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
|
||||
self.contentTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
|
@ -9,13 +9,181 @@ import SyncCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import UrlEscaping
|
||||
import ActivityIndicator
|
||||
|
||||
private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
private class TextField: UITextField, UIScrollViewDelegate {
|
||||
fileprivate func updatePrefixWidth(_ prefixWidth: CGFloat) {
|
||||
let previousPrefixWidth = self.prefixWidth
|
||||
self.prefixWidth = prefixWidth
|
||||
let leftOffset = prefixWidth
|
||||
if let scrollView = self.scrollView {
|
||||
if scrollView.contentInset.left != leftOffset {
|
||||
scrollView.contentInset = UIEdgeInsets(top: 0.0, left: leftOffset, bottom: 0.0, right: 0.0)
|
||||
}
|
||||
if leftOffset.isZero {
|
||||
scrollView.contentOffset = CGPoint()
|
||||
} else if self.prefixWidth != previousPrefixWidth {
|
||||
scrollView.contentOffset = CGPoint(x: -leftOffset, y: 0.0)
|
||||
}
|
||||
self.updatePrefixPosition(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private var prefixWidth: CGFloat = 0.0
|
||||
|
||||
let prefixLabel: ImmediateTextNode
|
||||
var prefixString: NSAttributedString? {
|
||||
didSet {
|
||||
self.prefixLabel.attributedText = self.prefixString
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.prefixLabel = ImmediateTextNode()
|
||||
self.prefixLabel.isUserInteractionEnabled = false
|
||||
self.prefixLabel.displaysAsynchronously = false
|
||||
self.prefixLabel.maximumNumberOfLines = 1
|
||||
self.prefixLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubnode(self.prefixLabel)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func addSubview(_ view: UIView) {
|
||||
super.addSubview(view)
|
||||
|
||||
if let scrollView = view as? UIScrollView {
|
||||
scrollView.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
private weak var _scrollView: UIScrollView?
|
||||
var scrollView: UIScrollView? {
|
||||
if let scrollView = self._scrollView {
|
||||
return scrollView
|
||||
}
|
||||
for view in self.subviews {
|
||||
if let scrollView = view as? UIScrollView {
|
||||
_scrollView = scrollView
|
||||
return scrollView
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func deleteBackward() {
|
||||
super.deleteBackward()
|
||||
|
||||
if let scrollView = self.scrollView {
|
||||
if scrollView.contentSize.width <= scrollView.frame.width && scrollView.contentOffset.x > -scrollView.contentInset.left {
|
||||
scrollView.contentOffset = CGPoint(x: max(scrollView.contentOffset.x - 5.0, -scrollView.contentInset.left), y: 0.0)
|
||||
self.updatePrefixPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fixAutoScroll: CGPoint?
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let fixAutoScroll = self.fixAutoScroll {
|
||||
self.scrollView?.setContentOffset(fixAutoScroll, animated: true)
|
||||
self.scrollView?.setContentOffset(fixAutoScroll, animated: false)
|
||||
self.fixAutoScroll = nil
|
||||
} else {
|
||||
self.updatePrefixPosition()
|
||||
}
|
||||
}
|
||||
|
||||
override func becomeFirstResponder() -> Bool {
|
||||
if let contentOffset = self.scrollView?.contentOffset {
|
||||
self.fixAutoScroll = contentOffset
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.fixAutoScroll = nil
|
||||
}
|
||||
}
|
||||
return super.becomeFirstResponder()
|
||||
}
|
||||
|
||||
private func updatePrefixPosition(transition: ContainedViewLayoutTransition = .immediate) {
|
||||
if let scrollView = self.scrollView {
|
||||
transition.updateFrame(node: self.prefixLabel, frame: CGRect(origin: CGPoint(x: -scrollView.contentOffset.x - scrollView.contentInset.left, y: self.prefixLabel.frame.minY), size: self.prefixLabel.frame.size))
|
||||
}
|
||||
}
|
||||
|
||||
override var keyboardAppearance: UIKeyboardAppearance {
|
||||
get {
|
||||
return super.keyboardAppearance
|
||||
}
|
||||
set {
|
||||
let resigning = self.isFirstResponder
|
||||
if resigning {
|
||||
self.resignFirstResponder()
|
||||
}
|
||||
super.keyboardAppearance = newValue
|
||||
if resigning {
|
||||
let _ = self.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
if bounds.size.width.isZero {
|
||||
return CGRect(origin: CGPoint(), size: CGSize())
|
||||
}
|
||||
var rect = bounds.insetBy(dx: 0.0, dy: 4.0)
|
||||
if #available(iOS 14.0, *) {
|
||||
} else {
|
||||
rect.origin.y += 1.0
|
||||
}
|
||||
if !self.prefixWidth.isZero && self.scrollView?.superview == nil {
|
||||
var offset = self.prefixWidth
|
||||
if let scrollView = self.scrollView {
|
||||
offset = scrollView.contentOffset.x * -1.0
|
||||
}
|
||||
rect.origin.x += offset
|
||||
rect.size.width -= offset
|
||||
}
|
||||
rect.size.width = max(rect.size.width, 10.0)
|
||||
return rect
|
||||
}
|
||||
|
||||
override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return self.textRect(forBounds: bounds)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let bounds = self.bounds
|
||||
if bounds.size.width.isZero {
|
||||
return
|
||||
}
|
||||
|
||||
var placeholderOffset: CGFloat = 0.0
|
||||
if #available(iOS 14.0, *) {
|
||||
placeholderOffset = 1.0
|
||||
} else {
|
||||
}
|
||||
|
||||
let textRect = self.textRect(forBounds: bounds)
|
||||
|
||||
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: floorToScreenPixels((bounds.height - prefixSize.height) / 2.0)), size: prefixSize)
|
||||
self.updatePrefixWidth(prefixSize.width)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private var theme: PresentationTheme
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textInputNode: EditableTextNode
|
||||
private let placeholderNode: ASTextNode
|
||||
private let prefixNode: ASTextNode
|
||||
// private let textInputNode: EditableTextNode
|
||||
private let textInputNode: TextField
|
||||
private let clearButton: HighlightableButtonNode
|
||||
|
||||
var updateHeight: (() -> Void)?
|
||||
@ -23,16 +191,15 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita
|
||||
var textChanged: ((String) -> Void)?
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
return self.textInputNode.attributedText?.string ?? ""
|
||||
}
|
||||
set {
|
||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||
if self.textInputNode.isFirstResponder() {
|
||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
if self.textInputNode.isFirstResponder {
|
||||
self.clearButton.isHidden = newValue.isEmpty
|
||||
} else {
|
||||
self.clearButton.isHidden = true
|
||||
@ -40,52 +207,41 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita
|
||||
}
|
||||
}
|
||||
|
||||
var placeholder: String = "" {
|
||||
var prefix: String = "" {
|
||||
didSet {
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
self.textInputNode.prefixString = NSAttributedString(string: self.prefix, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
var prefix: String = "" {
|
||||
var disabled: Bool = false {
|
||||
didSet {
|
||||
self.prefixNode.attributedText = NSAttributedString(string: self.prefix, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
self.clearButton.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
private let maxLength: Int
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String, maxLength: Int, returnKeyType: UIReturnKeyType = .done) {
|
||||
init(theme: PresentationTheme, placeholder: String, maxLength: Int, keyboardType: UIKeyboardType = .default, returnKeyType: UIReturnKeyType = .done) {
|
||||
self.theme = theme
|
||||
self.maxLength = maxLength
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
|
||||
self.textInputNode = EditableTextNode()
|
||||
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||
self.textInputNode = TextField()
|
||||
self.textInputNode.font = Font.regular(14.0)
|
||||
self.textInputNode.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
|
||||
self.textInputNode.clipsToBounds = true
|
||||
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0)
|
||||
// self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0)
|
||||
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textInputNode.keyboardType = .default
|
||||
self.textInputNode.keyboardType = keyboardType
|
||||
self.textInputNode.autocapitalizationType = .sentences
|
||||
self.textInputNode.returnKeyType = returnKeyType
|
||||
self.textInputNode.autocorrectionType = .default
|
||||
self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
|
||||
|
||||
self.placeholderNode = ASTextNode()
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
self.placeholderNode.displaysAsynchronously = false
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
|
||||
self.prefixNode = ASTextNode()
|
||||
self.prefixNode.isUserInteractionEnabled = false
|
||||
self.prefixNode.displaysAsynchronously = false
|
||||
self.prefixNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
|
||||
|
||||
self.clearButton = HighlightableButtonNode()
|
||||
self.clearButton.imageNode.displaysAsynchronously = false
|
||||
self.clearButton.imageNode.displayWithoutProcessing = true
|
||||
@ -94,24 +250,29 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita
|
||||
self.clearButton.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.textInputNode.delegate = self
|
||||
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textInputNode)
|
||||
self.addSubnode(self.placeholderNode)
|
||||
self.addSubnode(self.prefixNode)
|
||||
self.addSubnode(self.clearButton)
|
||||
|
||||
self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.textInputNode.delegate = self
|
||||
self.view.insertSubview(self.textInputNode, aboveSubview: self.backgroundNode.view)
|
||||
}
|
||||
|
||||
func selectAll() {
|
||||
self.textInputNode.selectAll(nil)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
|
||||
self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: [])
|
||||
}
|
||||
@ -126,10 +287,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
|
||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height)))
|
||||
transition.updateFrame(view: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height)))
|
||||
|
||||
if let image = self.clearButton.image(for: []) {
|
||||
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - 8.0 - image.size.width, y: backgroundFrame.minY + floor((backgroundFrame.size.height - image.size.height) / 2.0)), size: image.size))
|
||||
@ -139,48 +297,46 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.textInputNode.becomeFirstResponder()
|
||||
let _ = self.textInputNode.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func deactivateInput() {
|
||||
self.textInputNode.resignFirstResponder()
|
||||
}
|
||||
|
||||
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
self.textChanged?(editableTextNode.textView.text)
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
self.clearButton.isHidden = !self.placeholderNode.isHidden
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
self.clearButton.isHidden = (textField.text ?? "").isEmpty
|
||||
}
|
||||
|
||||
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.clearButton.isHidden = (editableTextNode.textView.text ?? "").isEmpty
|
||||
}
|
||||
|
||||
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
self.clearButton.isHidden = true
|
||||
}
|
||||
|
||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
func textFieldDidUpdateText(_ text: String) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
self.textChanged?(text)
|
||||
self.clearButton.isHidden = (text).isEmpty
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
if self.disabled {
|
||||
return false
|
||||
}
|
||||
let updatedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||
if updatedText.count > maxLength {
|
||||
self.textInputNode.layer.addShakeAnimation()
|
||||
return false
|
||||
}
|
||||
if text == "\n" {
|
||||
if string == "\n" {
|
||||
self.complete?()
|
||||
return false
|
||||
}
|
||||
self.textFieldDidUpdateText(updatedText)
|
||||
return true
|
||||
}
|
||||
|
||||
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
|
||||
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - 20.0, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||
|
||||
return min(61.0, max(33.0, unboundTextFieldHeight))
|
||||
return 33.0
|
||||
}
|
||||
|
||||
private func updateTextNodeText(animated: Bool) {
|
||||
@ -195,25 +351,72 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita
|
||||
}
|
||||
|
||||
@objc func clearPressed() {
|
||||
self.placeholderNode.isHidden = false
|
||||
self.clearButton.isHidden = true
|
||||
|
||||
self.textInputNode.attributedText = nil
|
||||
self.updateHeight?()
|
||||
self.textChanged?("")
|
||||
}
|
||||
}
|
||||
|
||||
private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
enum InfoText {
|
||||
case info
|
||||
case checking
|
||||
case available
|
||||
case taken
|
||||
case generating
|
||||
}
|
||||
private var theme: PresentationTheme
|
||||
private var alertTheme: AlertControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
private let title: String
|
||||
private let text: String
|
||||
|
||||
var infoText: InfoText? {
|
||||
didSet {
|
||||
let text: String
|
||||
let color: UIColor
|
||||
var activity = false
|
||||
if let infoText = self.infoText {
|
||||
switch infoText {
|
||||
case .info:
|
||||
text = self.strings.ImportStickerPack_ChooseLinkDescription
|
||||
color = self.alertTheme.primaryColor
|
||||
case .checking:
|
||||
text = self.strings.ImportStickerPack_CheckingLink
|
||||
color = self.alertTheme.secondaryColor
|
||||
activity = true
|
||||
case .available:
|
||||
text = self.strings.ImportStickerPack_LinkAvailable
|
||||
color = self.theme.list.freeTextSuccessColor
|
||||
case .taken:
|
||||
text = self.strings.ImportStickerPack_LinkTaken
|
||||
color = self.theme.list.freeTextErrorColor
|
||||
case .generating:
|
||||
text = self.strings.ImportStickerPack_GeneratingLink
|
||||
color = self.alertTheme.secondaryColor
|
||||
activity = true
|
||||
}
|
||||
self.activityIndicator.isHidden = !activity
|
||||
} else {
|
||||
text = self.text
|
||||
color = self.alertTheme.primaryColor
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(13.0), textColor: color)
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
private let activityIndicator: ActivityIndicator
|
||||
let inputFieldNode: ImportStickerPackTitleInputFieldNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
fileprivate let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
@ -227,13 +430,15 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
self.inputFieldNode.complete = self.complete
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int) {
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int, asciiOnly: Bool = false) {
|
||||
self.strings = strings
|
||||
self.alertTheme = theme
|
||||
self.theme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
|
||||
@ -242,7 +447,13 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 8
|
||||
|
||||
self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength)
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow)
|
||||
self.activityIndicator.isHidden = true
|
||||
|
||||
self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength, keyboardType: asciiOnly ? .asciiCapable : .default, returnKeyType: asciiOnly ? .done : .next)
|
||||
if asciiOnly {
|
||||
self.inputFieldNode.prefix = "t.me/addstickers/"
|
||||
}
|
||||
self.inputFieldNode.text = value ?? ""
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
@ -266,6 +477,7 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
|
||||
self.addSubnode(self.inputFieldNode)
|
||||
|
||||
@ -299,6 +511,8 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.alertTheme = theme
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
|
||||
@ -331,8 +545,13 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 4.0
|
||||
|
||||
let activitySize = CGSize(width: 20.0, height: 20.0)
|
||||
let textSize = self.textNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
let activityInset: CGFloat = self.activityIndicator.isHidden ? 0.0 : activitySize.width + 5.0
|
||||
let totalWidth = textSize.width + activityInset
|
||||
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: origin.y - 1.0), size: activitySize))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0) + activityInset, y: origin.y), size: textSize))
|
||||
|
||||
origin.y += textSize.height + 6.0 + spacing
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
@ -435,14 +654,15 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
func importStickerPackTitleController(sharedContext: SharedAccountContext, account: Account, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, maxLength: Int, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
func importStickerPackTitleController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, apply: @escaping (String?) -> Void, cancel: @escaping () -> Void) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var applyImpl: (() -> Void)?
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: {
|
||||
cancel()
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Next, action: {
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
@ -454,18 +674,30 @@ func importStickerPackTitleController(sharedContext: SharedAccountContext, accou
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
dismissImpl?(true)
|
||||
|
||||
let previousValue = value ?? ""
|
||||
let newValue = contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
apply(previousValue != newValue || value == nil ? newValue : nil)
|
||||
guard !newValue.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
contentNode.infoText = .generating
|
||||
contentNode.inputFieldNode.disabled = true
|
||||
contentNode.actionNodes.last?.actionEnabled = false
|
||||
|
||||
apply(newValue)
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
|
||||
})
|
||||
contentNode.actionNodes.last?.actionEnabled = false
|
||||
contentNode.inputFieldNode.textChanged = { [weak contentNode] title in
|
||||
contentNode?.actionNodes.last?.actionEnabled = !title.trimmingTrailingSpaces().isEmpty
|
||||
}
|
||||
controller.willDismiss = { [weak contentNode] in
|
||||
contentNode?.inputFieldNode.deactivateInput()
|
||||
}
|
||||
controller.dismissed = {
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
@ -481,18 +713,18 @@ func importStickerPackTitleController(sharedContext: SharedAccountContext, accou
|
||||
}
|
||||
|
||||
|
||||
func importStickerPackShortNameController(sharedContext: SharedAccountContext, account: Account, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, maxLength: Int, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
func importStickerPackShortNameController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, existingAlertController: AlertController?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var applyImpl: (() -> Void)?
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.ImportStickerPack_Create, action: {
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength)
|
||||
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, asciiOnly: true)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
@ -500,18 +732,62 @@ func importStickerPackShortNameController(sharedContext: SharedAccountContext, a
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
dismissImpl?(true)
|
||||
|
||||
let previousValue = value ?? ""
|
||||
let newValue = contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
apply(previousValue != newValue || value == nil ? newValue : nil)
|
||||
guard !newValue.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
dismissImpl?(true)
|
||||
apply(newValue)
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode, existingAlertController: existingAlertController)
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
|
||||
})
|
||||
let checkDisposable = MetaDisposable()
|
||||
var value = value ?? ""
|
||||
contentNode.actionNodes.last?.actionEnabled = !value.isEmpty
|
||||
if !value.isEmpty {
|
||||
Queue.mainQueue().after(0.25) {
|
||||
contentNode.inputFieldNode.selectAll()
|
||||
}
|
||||
}
|
||||
contentNode.inputFieldNode.textChanged = { [weak contentNode] value in
|
||||
if value.isEmpty {
|
||||
checkDisposable.set(nil)
|
||||
contentNode?.infoText = .info
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
} else {
|
||||
checkDisposable.set((context.engine.stickers.validateStickerSetShortNameInteractive(shortName: value)
|
||||
|> deliverOnMainQueue).start(next: { [weak contentNode] result in
|
||||
switch result {
|
||||
case .checking:
|
||||
contentNode?.infoText = .checking
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
case let .availability(availability):
|
||||
switch availability {
|
||||
case .available:
|
||||
contentNode?.infoText = .available
|
||||
contentNode?.actionNodes.last?.actionEnabled = true
|
||||
case .taken:
|
||||
contentNode?.infoText = .taken
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
case .invalid:
|
||||
contentNode?.infoText = .info
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
}
|
||||
case .invalidFormat:
|
||||
contentNode?.infoText = .info
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
controller.willDismiss = { [weak contentNode] in
|
||||
contentNode?.inputFieldNode.deactivateInput()
|
||||
}
|
||||
controller.dismissed = {
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
|
@ -119,14 +119,16 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
self.imageNode.image = image
|
||||
dimensions = image.size
|
||||
}
|
||||
case let .animation(data):
|
||||
case .animation:
|
||||
self.imageNode.isHidden = true
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
self.addSubnode(animationNode)
|
||||
|
||||
let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
// animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
||||
if let resource = stickerItem.resource {
|
||||
animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||
}
|
||||
animationNode.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
}
|
||||
} else {
|
||||
|
@ -68,26 +68,24 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
self.textNode = ASTextNode()
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
switch item.content {
|
||||
case let .image(data):
|
||||
self.imageNode.image = UIImage(data: data)
|
||||
case .animation:
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
let dimensions = PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
||||
if let resource = item.resource {
|
||||
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||
}
|
||||
self.animationNode?.visibility = true
|
||||
}
|
||||
if case let .image(data) = item.content, let image = UIImage(data: data) {
|
||||
self.imageNode.image = image
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: item.emojis.joined(separator: " "), font: Font.regular(32.0), textColor: .black)
|
||||
|
||||
// if item.file.isAnimatedSticker {
|
||||
// let animationNode = AnimatedStickerNode()
|
||||
// self.animationNode = animationNode
|
||||
//
|
||||
// let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
// let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
||||
//
|
||||
// self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||
// self.animationNode?.visibility = true
|
||||
// self.animationNode?.addSubnode(self.textNode)
|
||||
// } else {
|
||||
// self.imageNode.addSubnode(self.textNode)
|
||||
// self.animationNode = nil
|
||||
// }
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
@ -110,26 +108,11 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0), y: -textSize.height - textSpacing), size: textSize)
|
||||
|
||||
self.imageNode.frame = imageFrame
|
||||
return boundingSize
|
||||
|
||||
// if let dimensitons = self.item.file.dimensions {
|
||||
// let textSpacing: CGFloat = 10.0
|
||||
// let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0))
|
||||
//
|
||||
// let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
|
||||
// self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
// let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing), size: imageSize)
|
||||
// self.imageNode.frame = imageFrame
|
||||
// if let animationNode = self.animationNode {
|
||||
// animationNode.frame = imageFrame
|
||||
// animationNode.updateLayout(size: imageSize)
|
||||
// }
|
||||
//
|
||||
// self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0), y: -textSize.height - textSpacing), size: textSize)
|
||||
//
|
||||
// return CGSize(width: size.width, height: imageFrame.height + textSize.height + textSpacing)
|
||||
// } else {
|
||||
// return CGSize(width: size.width, height: 10.0)
|
||||
// }
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.frame = imageFrame
|
||||
animationNode.updateLayout(size: imageFrame.size)
|
||||
}
|
||||
return boundingSize
|
||||
}
|
||||
}
|
||||
|
@ -79,9 +79,9 @@ public enum CreateStickerSetError {
|
||||
}
|
||||
|
||||
public struct ImportSticker {
|
||||
let resource: MediaResource
|
||||
public let resource: MediaResource
|
||||
let emojis: [String]
|
||||
let dimensions: PixelDimensions
|
||||
public let dimensions: PixelDimensions
|
||||
|
||||
public init(resource: MediaResource, emojis: [String], dimensions: PixelDimensions) {
|
||||
self.resource = resource
|
||||
@ -200,6 +200,9 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
completeCount += 1
|
||||
case let .progress(progress):
|
||||
totalProgress += progress
|
||||
if progress == 1.0 {
|
||||
completeCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let normalizedProgress = min(1.0, max(0.0, totalProgress / Float(stickers.count)))
|
||||
@ -237,6 +240,9 @@ func _internal_stickerSetShortNameAvailability(account: Account, shortName: Stri
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
if error.errorDescription == "SHORT_NAME_OCCUPIED" {
|
||||
return .single(.taken)
|
||||
}
|
||||
return .single(.invalid)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -168,7 +168,13 @@ private func videoFirstFrameData(account: Account, resource: MediaResource, chun
|
||||
private func fetchCachedStickerAJpegRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedStickerAJpegRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||
return Signal({ subscriber in
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||
if let image = WebP.convert(fromWebP: data) {
|
||||
var image: UIImage?
|
||||
if let webpImage = WebP.convert(fromWebP: data) {
|
||||
image = webpImage
|
||||
} else if let pngImage = UIImage(data: data) {
|
||||
image = pngImage
|
||||
}
|
||||
if let image = image {
|
||||
let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))"
|
||||
let url = URL(fileURLWithPath: path)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "7.7.1",
|
||||
"app": "7.8",
|
||||
"bazel": "4.0.0",
|
||||
"xcode": "12.4"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user