mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
a603c138e9
commit
35e9887343
@ -11218,6 +11218,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"GroupBoost.AdditionalFeatures" = "Additional Features";
|
||||
"GroupBoost.AdditionalFeaturesText" = "By gaining **boosts**, your group reaches higher levels and unlocks more features.";
|
||||
"ChannelBoost.AdditionalFeaturesText" = "By gaining **boosts**, your channel reaches higher levels and unlocks more features.";
|
||||
|
||||
"Stats.Boosts.Group.NoBoostersYet" = "No users currently boost your group";
|
||||
"Stats.Boosts.Group.BoostersInfo" = "Your group is currently boosted by these members.";
|
||||
|
@ -935,7 +935,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
|
||||
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
||||
func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeBusinessSetupScreen(context: AccountContext) -> ViewController
|
||||
func makeChatbotSetupScreen(context: AccountContext) -> ViewController
|
||||
func makeBusinessLocationSetupScreen(context: AccountContext, initialValue: TelegramBusinessLocation?, completion: @escaping (TelegramBusinessLocation?) -> Void) -> ViewController
|
||||
|
@ -70,6 +70,14 @@ public enum PremiumDemoSubject {
|
||||
case messageTags
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case folderTags
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
case businessGreetingMessage
|
||||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
}
|
||||
|
||||
public enum PremiumLimitSubject {
|
||||
|
@ -5617,7 +5617,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
private func openFilterSettings() {
|
||||
self.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(false)
|
||||
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: true, dismissed: { [weak self] in
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: true, scrollToTags: false, dismissed: { [weak self] in
|
||||
self?.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(true)
|
||||
})
|
||||
navigationController.pushViewController(controller)
|
||||
|
@ -41,6 +41,19 @@ private enum ChatListFilterPresetListSection: Int32 {
|
||||
case tags
|
||||
}
|
||||
|
||||
public enum ChatListFilterPresetListEntryTag: ItemListItemTag {
|
||||
case displayTags
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? ChatListFilterPresetListEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String {
|
||||
if peers.isEmpty {
|
||||
return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder
|
||||
@ -192,7 +205,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
//TODO:localize
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Show Folder Tags", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateDisplayTags(value)
|
||||
})
|
||||
}, tag: ChatListFilterPresetListEntryTag.displayTags)
|
||||
case .displayTagsFooter:
|
||||
//TODO:localize
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain("Display folder names for each chat in the chat list."), sectionId: self.section)
|
||||
@ -285,7 +298,7 @@ public enum ChatListFilterPresetListControllerMode {
|
||||
case modal
|
||||
}
|
||||
|
||||
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, scrollToTags: Bool = false, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
let initialState = ChatListFilterPresetListControllerState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -627,7 +640,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatListFolderSettings_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits), style: .blocks, animateChanges: true)
|
||||
let entries = chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, initialScrollToItem: scrollToTags ? ListViewScrollToItem(index: entries.count - 1, position: .center(.bottom), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down) : nil, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -889,11 +889,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
})
|
||||
|
||||
if case let .share(_, selfPeer, _) = self.mode {
|
||||
switch self.mode {
|
||||
case let .share(_, selfPeer, _):
|
||||
if let selfPeer {
|
||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
|
||||
}
|
||||
self.headerNode.mapNode.hasPickerAnnotation = true
|
||||
case .pick:
|
||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, location: TelegramMediaMap(coordinate: CLLocationCoordinate2DMake(0, 0)), queryId: nil, resultId: nil, forcedSelection: true)
|
||||
self.headerNode.mapNode.hasPickerAnnotation = true
|
||||
}
|
||||
|
||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
|
||||
|
BIN
submodules/PremiumUI/Resources/badge
Normal file
BIN
submodules/PremiumUI/Resources/badge
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/boost
Normal file
BIN
submodules/PremiumUI/Resources/boost
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/business.png
Normal file
BIN
submodules/PremiumUI/Resources/business.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
submodules/PremiumUI/Resources/business.scn
Normal file
BIN
submodules/PremiumUI/Resources/business.scn
Normal file
Binary file not shown.
BIN
submodules/PremiumUI/Resources/coin
Normal file
BIN
submodules/PremiumUI/Resources/coin
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/emoji
Normal file
BIN
submodules/PremiumUI/Resources/emoji
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/gift
Normal file
BIN
submodules/PremiumUI/Resources/gift
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/lightspeed
Normal file
BIN
submodules/PremiumUI/Resources/lightspeed
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/star
Normal file
BIN
submodules/PremiumUI/Resources/star
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/swirl
Normal file
BIN
submodules/PremiumUI/Resources/swirl
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/tag
Normal file
BIN
submodules/PremiumUI/Resources/tag
Normal file
Binary file not shown.
Binary file not shown.
61
submodules/PremiumUI/Sources/BadgeBusinessView.swift
Normal file
61
submodules/PremiumUI/Sources/BadgeBusinessView.swift
Normal file
@ -0,0 +1,61 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SceneKit
|
||||
import Display
|
||||
import AppBundle
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class BadgeBusinessView: UIView, PhoneDemoDecorationView {
|
||||
private let sceneView: SCNView
|
||||
|
||||
private var leftParticles: SCNNode?
|
||||
private var rightParticles: SCNNode?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let scene = loadCompressedScene(name: "business", version: sceneVersion) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.alpha = 0.0
|
||||
|
||||
self.addSubview(self.sceneView)
|
||||
|
||||
self.leftParticles = self.sceneView.scene?.rootNode.childNode(withName: "leftParticles", recursively: false)
|
||||
self.rightParticles = self.sceneView.scene?.rootNode.childNode(withName: "rightParticles", recursively: false)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setVisible(_ visible: Bool) {
|
||||
if visible, let leftParticles = self.leftParticles, let rightParticles = self.rightParticles, leftParticles.parent == nil {
|
||||
self.sceneView.scene?.rootNode.addChildNode(leftParticles)
|
||||
self.sceneView.scene?.rootNode.addChildNode(rightParticles)
|
||||
}
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
|
||||
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.5 : 0.0, completion: { [weak self] finished in
|
||||
if let strongSelf = self, finished && !visible && strongSelf.leftParticles?.parent != nil {
|
||||
strongSelf.leftParticles?.removeFromParentNode()
|
||||
strongSelf.rightParticles?.removeFromParentNode()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func resetAnimation() {
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ final class BadgeStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "badge", version: 1) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
@ -67,8 +67,8 @@ final class EmojiStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "emoji", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "emoji", version: 1) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
@ -121,8 +121,8 @@ final class TagStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "tag", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "tag", version: 1) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
@ -12,7 +12,7 @@ import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
public final class BoostHeaderBackgroundComponent: Component {
|
||||
let isVisible: Bool
|
||||
@ -58,7 +58,7 @@ public final class BoostHeaderBackgroundComponent: Component {
|
||||
|
||||
|
||||
private func setup() {
|
||||
guard let url = getAppBundle().url(forResource: "boost", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "boost", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,6 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EmojiStatusComponent
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
|
||||
class EmojiHeaderComponent: Component {
|
||||
let context: AccountContext
|
||||
let animationCache: AnimationCache
|
||||
|
@ -5,6 +5,8 @@ import Display
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
||||
private let sceneView: SCNView
|
||||
|
||||
@ -13,8 +15,8 @@ final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "lightspeed", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "lightspeed", version: sceneVersion) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
@ -14,7 +14,7 @@ import MergedAvatarsNode
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class GiftAvatarComponent: Component {
|
||||
let context: AccountContext
|
||||
@ -106,7 +106,7 @@ final class GiftAvatarComponent: Component {
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "gift", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -371,6 +371,7 @@ final class PhoneDemoComponent: Component {
|
||||
case emoji
|
||||
case hello
|
||||
case tag
|
||||
case business
|
||||
}
|
||||
|
||||
enum Model {
|
||||
@ -547,6 +548,13 @@ final class PhoneDemoComponent: Component {
|
||||
self.decorationView = starsView
|
||||
self.decorationContainerView.addSubview(starsView)
|
||||
}
|
||||
case .business:
|
||||
if let _ = self.decorationView as? BadgeBusinessView {
|
||||
} else {
|
||||
let starsView = BadgeBusinessView(frame: self.decorationContainerView.bounds)
|
||||
self.decorationView = starsView
|
||||
self.decorationContainerView.addSubview(starsView)
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)
|
||||
|
@ -698,7 +698,7 @@ private final class SheetContent: CombinedComponent {
|
||||
isCurrent = mode == .current
|
||||
}
|
||||
case .features:
|
||||
textString = strings.GroupBoost_AdditionalFeaturesText
|
||||
textString = isGroup ? strings.GroupBoost_AdditionalFeaturesText : strings.ChannelBoost_AdditionalFeaturesText
|
||||
}
|
||||
|
||||
let defaultTitle = strings.ChannelBoost_Level("\(level)").string
|
||||
|
@ -8,7 +8,7 @@ import GZip
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
private let sceneVersion: Int = 2
|
||||
|
||||
private func deg2rad(_ number: Float) -> Float {
|
||||
return number * .pi / 180
|
||||
@ -223,24 +223,7 @@ class PremiumCoinComponent: Component {
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
let resourceUrl: URL
|
||||
if let url = getAppBundle().url(forResource: "coin", withExtension: "scn") {
|
||||
resourceUrl = url
|
||||
} else {
|
||||
let fileName = "coin_\(sceneVersion).scn"
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
|
||||
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
guard let url = getAppBundle().url(forResource: "coin", withExtension: ""),
|
||||
let compressedData = try? Data(contentsOf: url),
|
||||
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
|
||||
return
|
||||
}
|
||||
try? decompressedData.write(to: tmpUrl)
|
||||
}
|
||||
resourceUrl = tmpUrl
|
||||
}
|
||||
|
||||
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "coin", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -316,8 +299,8 @@ class PremiumCoinComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let fromScale: Float = 0.85
|
||||
let toScale: Float = 0.9
|
||||
let fromScale: Float = 0.9
|
||||
let toScale: Float = 1.0
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "scale")
|
||||
animation.duration = 2.0
|
||||
|
@ -1177,7 +1177,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
text = strings.Premium_LastSeenInfo
|
||||
case .messagePrivacy:
|
||||
text = strings.Premium_MessagePrivacyInfo
|
||||
case .doubleLimits, .stories, .business:
|
||||
default:
|
||||
text = ""
|
||||
}
|
||||
|
||||
@ -1392,6 +1392,14 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case business
|
||||
case folderTags
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
case businessGreetingMessage
|
||||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
}
|
||||
|
||||
public enum Source: Equatable {
|
||||
|
@ -533,6 +533,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
||||
demoSubject = .messagePrivacy
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
default:
|
||||
demoSubject = .doubleLimits
|
||||
}
|
||||
|
||||
let buttonText: String
|
||||
|
@ -439,6 +439,15 @@ public enum PremiumPerk: CaseIterable {
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case business
|
||||
case folderTags
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
case businessGreetingMessage
|
||||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
|
||||
|
||||
public static var allCases: [PremiumPerk] {
|
||||
return [
|
||||
@ -520,6 +529,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return "message_privacy"
|
||||
case .business:
|
||||
return "business"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -567,6 +578,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return strings.Premium_MessagePrivacy
|
||||
case .business:
|
||||
return strings.Premium_Business
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,6 +627,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return strings.Premium_MessagePrivacyInfo
|
||||
case .business:
|
||||
return strings.Premium_BusinessInfo
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -661,6 +676,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return "Premium/Perk/MessagePrivacy"
|
||||
case .business:
|
||||
return "Premium/Perk/Business"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1948,75 +1965,77 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
))),
|
||||
action: { [weak state] _ in
|
||||
var demoSubject: PremiumDemoScreen.Subject
|
||||
switch perk {
|
||||
case .doubleLimits:
|
||||
demoSubject = .doubleLimits
|
||||
case .moreUpload:
|
||||
demoSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
demoSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
demoSubject = .voiceToText
|
||||
case .noAds:
|
||||
demoSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
demoSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
demoSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
demoSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
demoSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
case .emojiStatus:
|
||||
demoSubject = .emojiStatus
|
||||
case .translation:
|
||||
demoSubject = .translation
|
||||
case .stories:
|
||||
demoSubject = .stories
|
||||
case .colors:
|
||||
demoSubject = .colors
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumColorsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .wallpapers:
|
||||
demoSubject = .wallpapers
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumWallpapersBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messageTags:
|
||||
demoSubject = .messageTags
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .lastSeen:
|
||||
demoSubject = .lastSeen
|
||||
let _ = ApplicationSpecificNotice.setDismissedLastSeenBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messagePrivacy:
|
||||
demoSubject = .messagePrivacy
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
switch perk {
|
||||
case .doubleLimits:
|
||||
demoSubject = .doubleLimits
|
||||
case .moreUpload:
|
||||
demoSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
demoSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
demoSubject = .voiceToText
|
||||
case .noAds:
|
||||
demoSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
demoSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
demoSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
demoSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
demoSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
case .emojiStatus:
|
||||
demoSubject = .emojiStatus
|
||||
case .translation:
|
||||
demoSubject = .translation
|
||||
case .stories:
|
||||
demoSubject = .stories
|
||||
case .colors:
|
||||
demoSubject = .colors
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumColorsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .wallpapers:
|
||||
demoSubject = .wallpapers
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumWallpapersBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messageTags:
|
||||
demoSubject = .messageTags
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .lastSeen:
|
||||
demoSubject = .lastSeen
|
||||
let _ = ApplicationSpecificNotice.setDismissedLastSeenBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messagePrivacy:
|
||||
demoSubject = .messagePrivacy
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
default:
|
||||
demoSubject = .doubleLimits
|
||||
}
|
||||
|
||||
let isPremium = state?.isPremium == true
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.perks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
|
||||
let isPremium = state?.isPremium == true
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.perks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
|
||||
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
|
||||
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
|
||||
}
|
||||
))))
|
||||
i += 1
|
||||
@ -2100,43 +2119,78 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
foregroundColor: .white,
|
||||
iconName: perk.iconName
|
||||
))),
|
||||
action: { _ in
|
||||
switch perk {
|
||||
case .location:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessLocation in
|
||||
guard let accountContext else {
|
||||
return
|
||||
action: { [weak state] _ in
|
||||
let isPremium = state?.isPremium == true
|
||||
if isPremium {
|
||||
switch perk {
|
||||
case .location:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessLocation in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in }))
|
||||
})
|
||||
case .hours:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessHours in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in }))
|
||||
})
|
||||
case .quickReplies:
|
||||
let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
case .greetings:
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, isAwayMode: false))
|
||||
case .awayMessages:
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, isAwayMode: true))
|
||||
case .chatbots:
|
||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext))
|
||||
}
|
||||
} else {
|
||||
var demoSubject: PremiumDemoScreen.Subject
|
||||
switch perk {
|
||||
case .location:
|
||||
demoSubject = .businessLocation
|
||||
case .hours:
|
||||
demoSubject = .businessHours
|
||||
case .quickReplies:
|
||||
demoSubject = .businessQuickReplies
|
||||
case .greetings:
|
||||
demoSubject = .businessGreetingMessage
|
||||
case .awayMessages:
|
||||
demoSubject = .businessAwayMessage
|
||||
case .chatbots:
|
||||
demoSubject = .businessChatBots
|
||||
}
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: [.businessLocation, .businessHours, .businessQuickReplies, .businessGreetingMessage, .businessAwayMessage, .businessChatBots], buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in }))
|
||||
})
|
||||
case .hours:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessHours in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in }))
|
||||
})
|
||||
case .quickReplies:
|
||||
let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
case .greetings:
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, isAwayMode: false))
|
||||
case .awayMessages:
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, isAwayMode: true))
|
||||
case .chatbots:
|
||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext))
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
}
|
||||
}
|
||||
))))
|
||||
@ -2238,7 +2292,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
iconName: "Premium/BusinessPerk/Tag"
|
||||
))),
|
||||
action: { _ in
|
||||
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, dismissed: nil))
|
||||
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil))
|
||||
}
|
||||
))))
|
||||
|
||||
|
@ -833,6 +833,129 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
availableItems[.businessLocation] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessLocation,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["business_location"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_Location,
|
||||
text: strings.Business_LocationInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessHours] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessHours,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["business_hours"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_OpeningHours,
|
||||
text: strings.Business_OpeningHoursInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessQuickReplies] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessQuickReplies,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["greeting_message"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_QuickReplies,
|
||||
text: strings.Business_QuickRepliesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessGreetingMessage] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessGreetingMessage,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["greeting_message"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_GreetingMessages,
|
||||
text: strings.Business_GreetingMessagesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessAwayMessage] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessAwayMessage,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["away_message"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_AwayMessages,
|
||||
text: strings.Business_AwayMessagesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessChatBots] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessChatBots,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["business_bots"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_Chatbots,
|
||||
text: strings.Business_ChatbotsInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
if let order = controller.order {
|
||||
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
|
||||
|
@ -8,7 +8,7 @@ import GZip
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
private let sceneVersion: Int = 6
|
||||
private let sceneVersion: Int = 7
|
||||
|
||||
private func deg2rad(_ number: Float) -> Float {
|
||||
return number * .pi / 180
|
||||
@ -45,7 +45,31 @@ private func generateDiffuseTexture() -> UIImage {
|
||||
})!
|
||||
}
|
||||
|
||||
class PremiumStarComponent: Component {
|
||||
func loadCompressedScene(name: String, version: Int) -> SCNScene? {
|
||||
let resourceUrl: URL
|
||||
if let url = getAppBundle().url(forResource: name, withExtension: "scn") {
|
||||
resourceUrl = url
|
||||
} else {
|
||||
let fileName = "\(name)_\(version).scn"
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
|
||||
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
guard let url = getAppBundle().url(forResource: name, withExtension: ""),
|
||||
let compressedData = try? Data(contentsOf: url),
|
||||
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
|
||||
return nil
|
||||
}
|
||||
try? decompressedData.write(to: tmpUrl)
|
||||
}
|
||||
resourceUrl = tmpUrl
|
||||
}
|
||||
|
||||
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
|
||||
return nil
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
final class PremiumStarComponent: Component {
|
||||
let isIntro: Bool
|
||||
let isVisible: Bool
|
||||
let hasIdleAnimations: Bool
|
||||
@ -251,24 +275,7 @@ class PremiumStarComponent: Component {
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
let resourceUrl: URL
|
||||
if let url = getAppBundle().url(forResource: "star", withExtension: "scn") {
|
||||
resourceUrl = url
|
||||
} else {
|
||||
let fileName = "star_\(sceneVersion).scn"
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
|
||||
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
guard let url = getAppBundle().url(forResource: "star", withExtension: ""),
|
||||
let compressedData = try? Data(contentsOf: url),
|
||||
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
|
||||
return
|
||||
}
|
||||
try? decompressedData.write(to: tmpUrl)
|
||||
}
|
||||
resourceUrl = tmpUrl
|
||||
}
|
||||
|
||||
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "star", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import Display
|
||||
import AppBundle
|
||||
import SwiftSignalKit
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
||||
private let sceneView: SCNView
|
||||
|
||||
@ -13,8 +15,8 @@ final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "swirl", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "swirl", version: sceneVersion) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
@ -16,6 +16,8 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
public let text: String
|
||||
public let actionTitle: String?
|
||||
public let action: () -> Void
|
||||
public let additionalActionTitle: String?
|
||||
public let additionalAction: () -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -24,7 +26,9 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
title: String,
|
||||
text: String,
|
||||
actionTitle: String?,
|
||||
action: @escaping () -> Void
|
||||
action: @escaping () -> Void,
|
||||
additionalActionTitle: String?,
|
||||
additionalAction: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
@ -33,6 +37,8 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
self.text = text
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
self.additionalActionTitle = additionalActionTitle
|
||||
self.additionalAction = additionalAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool {
|
||||
@ -54,6 +60,9 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
if lhs.actionTitle != rhs.actionTitle {
|
||||
return false
|
||||
}
|
||||
if lhs.additionalActionTitle != rhs.additionalActionTitle {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -65,6 +74,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private var button: ComponentView<Empty>?
|
||||
private var additionalButton: ComponentView<Empty>?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -139,7 +149,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 240.0, height: 50.0)
|
||||
containerSize: CGSize(width: 260.0, height: 50.0)
|
||||
)
|
||||
} else {
|
||||
if let button = self.button {
|
||||
@ -148,14 +158,52 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
var additionalButtonSize: CGSize?
|
||||
if let additionalActionTitle = component.additionalActionTitle {
|
||||
let additionalButton: ComponentView<Empty>
|
||||
if let current = self.additionalButton {
|
||||
additionalButton = current
|
||||
} else {
|
||||
additionalButton = ComponentView()
|
||||
self.additionalButton = additionalButton
|
||||
}
|
||||
|
||||
additionalButtonSize = additionalButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Text(
|
||||
text: additionalActionTitle, font:
|
||||
Font.regular(17.0),
|
||||
color: component.theme.list.itemAccentColor)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.additionalAction()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 262.0, height: 50.0)
|
||||
)
|
||||
} else {
|
||||
if let additionalButton = self.additionalButton {
|
||||
self.additionalButton = nil
|
||||
additionalButton.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let animationSpacing: CGFloat = 11.0
|
||||
let titleSpacing: CGFloat = 17.0
|
||||
let buttonSpacing: CGFloat = 17.0
|
||||
let buttonSpacing: CGFloat = 21.0
|
||||
|
||||
var totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height
|
||||
if let buttonSize {
|
||||
totalHeight += buttonSpacing + buttonSize.height
|
||||
}
|
||||
if let additionalButtonSize {
|
||||
totalHeight += buttonSpacing + additionalButtonSize.height
|
||||
}
|
||||
|
||||
var contentY = floor((availableSize.height - totalHeight) * 0.5)
|
||||
|
||||
@ -185,7 +233,14 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: contentY), size: buttonSize))
|
||||
contentY += buttonSize.height
|
||||
contentY += buttonSize.height + buttonSpacing
|
||||
}
|
||||
if let additionalButtonSize, let additionalButtonView = self.additionalButton?.view {
|
||||
if additionalButtonView.superview == nil {
|
||||
self.addSubview(additionalButtonView)
|
||||
}
|
||||
transition.setFrame(view: additionalButtonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - additionalButtonSize.width) * 0.5), y: contentY), size: additionalButtonSize))
|
||||
contentY += additionalButtonSize.height
|
||||
}
|
||||
|
||||
return availableSize
|
||||
|
@ -178,7 +178,11 @@ public final class ListActionItemComponent: Component {
|
||||
let contentRightInset: CGFloat
|
||||
switch component.accessory {
|
||||
case .none:
|
||||
contentRightInset = 16.0
|
||||
if let _ = component.icon {
|
||||
contentRightInset = 42.0
|
||||
} else {
|
||||
contentRightInset = 16.0
|
||||
}
|
||||
case .arrow:
|
||||
contentRightInset = 30.0
|
||||
case .toggle:
|
||||
@ -189,7 +193,7 @@ public final class ListActionItemComponent: Component {
|
||||
contentHeight += verticalInset
|
||||
|
||||
if component.leftIcon != nil {
|
||||
contentLeftInset += 46.0
|
||||
contentLeftInset += 52.0
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
@ -239,7 +243,7 @@ public final class ListActionItemComponent: Component {
|
||||
|
||||
var iconOffset: CGFloat = 0.0
|
||||
if case .none = component.accessory {
|
||||
iconOffset = 6.0
|
||||
iconOffset = 26.0
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - contentRightInset - iconSize.width + iconOffset, y: floor((contentHeight - iconSize.height) * 0.5)), size: iconSize)
|
||||
|
@ -8795,7 +8795,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
})
|
||||
case .chatFolders:
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, dismissed: nil)
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, scrollToTags: false, dismissed: nil)
|
||||
push(controller)
|
||||
case .notificationsAndSounds:
|
||||
if let settings = self.data?.globalSettings {
|
||||
|
@ -295,7 +295,23 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
let _ = paneNode.scrollToTop()
|
||||
}
|
||||
|
||||
func openCreateStory() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if let rootController = component.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||
let coordinator = rootController.openStoryCamera(customTarget: nil, transitionIn: nil, transitionedIn: {}, transitionOut: { _, _ in return nil })
|
||||
coordinator?.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
private var isUpdating = false
|
||||
func update(component: PeerInfoStoryGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
@ -313,7 +329,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
|
||||
var bottomInset: CGFloat = environment.safeInsets.bottom
|
||||
|
||||
if self.selectedCount != 0 {
|
||||
if self.selectedCount != 0 || (component.scope == .saved && self.paneNode?.isEmpty == false) {
|
||||
let selectionPanel: ComponentView<Empty>
|
||||
var selectionPanelTransition = transition
|
||||
if let current = self.selectionPanel {
|
||||
@ -327,7 +343,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
let buttonText: String
|
||||
switch component.scope {
|
||||
case .saved:
|
||||
buttonText = environment.strings.ChatList_Context_Archive
|
||||
buttonText = self.selectedCount > 0 ? environment.strings.ChatList_Context_Archive : environment.strings.StoryList_SavedAddAction
|
||||
case .archive:
|
||||
buttonText = environment.strings.StoryList_SaveToProfile
|
||||
}
|
||||
@ -344,7 +360,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
guard let paneNode = self.paneNode, !paneNode.selectedIds.isEmpty else {
|
||||
guard let paneNode = self.paneNode else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -361,21 +377,25 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
switch component.scope {
|
||||
case .saved:
|
||||
let selectedCount = paneNode.selectedItems.count
|
||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
|
||||
|
||||
paneNode.setIsSelectionModeActive(false)
|
||||
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||
|
||||
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
|
||||
environment.controller()?.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: title, timeout: nil, customUndoText: nil),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
if selectedCount == 0 {
|
||||
self.openCreateStory()
|
||||
} else {
|
||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
|
||||
|
||||
paneNode.setIsSelectionModeActive(false)
|
||||
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||
|
||||
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
|
||||
environment.controller()?.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: title, timeout: nil, customUndoText: nil),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
}
|
||||
case .archive:
|
||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start()
|
||||
|
||||
@ -449,10 +469,28 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
},
|
||||
listContext: nil
|
||||
)
|
||||
paneNode.isEmptyUpdated = { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
self.paneNode = paneNode
|
||||
self.addSubview(paneNode.view)
|
||||
if let selectionPanelView = self.selectionPanel?.view {
|
||||
self.insertSubview(paneNode.view, belowSubview: selectionPanelView)
|
||||
} else {
|
||||
self.addSubview(paneNode.view)
|
||||
}
|
||||
|
||||
paneNode.emptyAction = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openCreateStory()
|
||||
}
|
||||
paneNode.additionalEmptyAction = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
@ -948,6 +948,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
}
|
||||
|
||||
public var isEmptyUpdated: (Bool) -> Void = { _ in }
|
||||
|
||||
public private(set) var isSelectionModeActive: Bool
|
||||
|
||||
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
||||
@ -985,6 +987,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
public var openCurrentDate: (() -> Void)?
|
||||
public var paneDidScroll: (() -> Void)?
|
||||
public var emptyAction: (() -> Void)?
|
||||
public var additionalEmptyAction: (() -> Void)?
|
||||
|
||||
public var ensureRectVisible: ((UIView, CGRect) -> Void)?
|
||||
|
||||
@ -1729,6 +1732,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) {
|
||||
self.items = items
|
||||
self.isEmptyUpdated(self.isEmpty)
|
||||
|
||||
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
||||
var gridSnapshot: UIView?
|
||||
@ -2027,14 +2031,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
context: self.context,
|
||||
theme: presentationData.theme,
|
||||
animationName: "StoryListEmpty",
|
||||
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title,
|
||||
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text,
|
||||
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
|
||||
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
|
||||
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedAddAction,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.emptyAction?()
|
||||
},
|
||||
additionalActionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||
additionalAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.additionalEmptyAction?()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
@ -134,6 +134,9 @@ final class StoryAuthorInfoComponent: Component {
|
||||
if timeString.count < 6 {
|
||||
combinedString.append(NSAttributedString(string: " • \(timeString)", font: Font.regular(11.0), textColor: subtitleColor))
|
||||
}
|
||||
if component.isEdited {
|
||||
combinedString.append(NSAttributedString(string: " • \(component.strings.Story_HeaderEdited)", font: Font.regular(11.0), textColor: subtitleColor))
|
||||
}
|
||||
subtitle = combinedString
|
||||
subtitleTruncationType = .middle
|
||||
} else {
|
||||
|
@ -1882,8 +1882,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return archiveSettingsController(context: context)
|
||||
}
|
||||
|
||||
public func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController {
|
||||
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, dismissed: dismissed)
|
||||
public func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController {
|
||||
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, scrollToTags: scrollToTags, dismissed: dismissed)
|
||||
}
|
||||
|
||||
public func makeBusinessSetupScreen(context: AccountContext) -> ViewController {
|
||||
@ -2040,6 +2040,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSubject = .lastSeen
|
||||
case .messagePrivacy:
|
||||
mappedSubject = .messagePrivacy
|
||||
default:
|
||||
mappedSubject = .doubleLimits
|
||||
}
|
||||
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user