mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +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.AdditionalFeatures" = "Additional Features";
|
||||||
"GroupBoost.AdditionalFeaturesText" = "By gaining **boosts**, your group reaches higher levels and unlocks more 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.NoBoostersYet" = "No users currently boost your group";
|
||||||
"Stats.Boosts.Group.BoostersInfo" = "Your group is currently boosted by these members.";
|
"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 makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
|
||||||
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||||
func makeArchiveSettingsController(context: AccountContext) -> 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 makeBusinessSetupScreen(context: AccountContext) -> ViewController
|
||||||
func makeChatbotSetupScreen(context: AccountContext) -> ViewController
|
func makeChatbotSetupScreen(context: AccountContext) -> ViewController
|
||||||
func makeBusinessLocationSetupScreen(context: AccountContext, initialValue: TelegramBusinessLocation?, completion: @escaping (TelegramBusinessLocation?) -> Void) -> ViewController
|
func makeBusinessLocationSetupScreen(context: AccountContext, initialValue: TelegramBusinessLocation?, completion: @escaping (TelegramBusinessLocation?) -> Void) -> ViewController
|
||||||
|
|||||||
@ -70,6 +70,14 @@ public enum PremiumDemoSubject {
|
|||||||
case messageTags
|
case messageTags
|
||||||
case lastSeen
|
case lastSeen
|
||||||
case messagePrivacy
|
case messagePrivacy
|
||||||
|
case folderTags
|
||||||
|
|
||||||
|
case businessLocation
|
||||||
|
case businessHours
|
||||||
|
case businessGreetingMessage
|
||||||
|
case businessQuickReplies
|
||||||
|
case businessAwayMessage
|
||||||
|
case businessChatBots
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PremiumLimitSubject {
|
public enum PremiumLimitSubject {
|
||||||
|
|||||||
@ -5617,7 +5617,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
private func openFilterSettings() {
|
private func openFilterSettings() {
|
||||||
self.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(false)
|
self.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(false)
|
||||||
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
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)
|
self?.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(true)
|
||||||
})
|
})
|
||||||
navigationController.pushViewController(controller)
|
navigationController.pushViewController(controller)
|
||||||
|
|||||||
@ -41,6 +41,19 @@ private enum ChatListFilterPresetListSection: Int32 {
|
|||||||
case tags
|
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 {
|
private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String {
|
||||||
if peers.isEmpty {
|
if peers.isEmpty {
|
||||||
return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder
|
return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder
|
||||||
@ -192,7 +205,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
|||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Show Folder Tags", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Show Folder Tags", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateDisplayTags(value)
|
arguments.updateDisplayTags(value)
|
||||||
})
|
}, tag: ChatListFilterPresetListEntryTag.displayTags)
|
||||||
case .displayTagsFooter:
|
case .displayTagsFooter:
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain("Display folder names for each chat in the chat list."), sectionId: self.section)
|
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
|
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 initialState = ChatListFilterPresetListControllerState()
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
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 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))
|
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 {
|
if let selfPeer {
|
||||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
|
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
|
||||||
}
|
}
|
||||||
self.headerNode.mapNode.hasPickerAnnotation = true
|
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
|
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) {
|
override init(frame: CGRect) {
|
||||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
self.sceneView.backgroundColor = .clear
|
self.sceneView.backgroundColor = .clear
|
||||||
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
|
if let scene = loadCompressedScene(name: "badge", version: 1) {
|
||||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
self.sceneView.scene = scene
|
||||||
}
|
}
|
||||||
self.sceneView.isUserInteractionEnabled = false
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
self.sceneView.preferredFramesPerSecond = 60
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
@ -67,8 +67,8 @@ final class EmojiStarsView: UIView, PhoneDemoDecorationView {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
self.sceneView.backgroundColor = .clear
|
self.sceneView.backgroundColor = .clear
|
||||||
if let url = getAppBundle().url(forResource: "emoji", withExtension: "scn") {
|
if let scene = loadCompressedScene(name: "emoji", version: 1) {
|
||||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
self.sceneView.scene = scene
|
||||||
}
|
}
|
||||||
self.sceneView.isUserInteractionEnabled = false
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
self.sceneView.preferredFramesPerSecond = 60
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
@ -121,8 +121,8 @@ final class TagStarsView: UIView, PhoneDemoDecorationView {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
self.sceneView.backgroundColor = .clear
|
self.sceneView.backgroundColor = .clear
|
||||||
if let url = getAppBundle().url(forResource: "tag", withExtension: "scn") {
|
if let scene = loadCompressedScene(name: "tag", version: 1) {
|
||||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
self.sceneView.scene = scene
|
||||||
}
|
}
|
||||||
self.sceneView.isUserInteractionEnabled = false
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
self.sceneView.preferredFramesPerSecond = 60
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import TelegramCore
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
private let sceneVersion: Int = 3
|
private let sceneVersion: Int = 1
|
||||||
|
|
||||||
public final class BoostHeaderBackgroundComponent: Component {
|
public final class BoostHeaderBackgroundComponent: Component {
|
||||||
let isVisible: Bool
|
let isVisible: Bool
|
||||||
@ -58,7 +58,7 @@ public final class BoostHeaderBackgroundComponent: Component {
|
|||||||
|
|
||||||
|
|
||||||
private func setup() {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,6 @@ import AnimationCache
|
|||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
|
|
||||||
private let sceneVersion: Int = 3
|
|
||||||
|
|
||||||
class EmojiHeaderComponent: Component {
|
class EmojiHeaderComponent: Component {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let animationCache: AnimationCache
|
let animationCache: AnimationCache
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import Display
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
|
||||||
|
private let sceneVersion: Int = 1
|
||||||
|
|
||||||
final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
||||||
private let sceneView: SCNView
|
private let sceneView: SCNView
|
||||||
|
|
||||||
@ -13,8 +15,8 @@ final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
self.sceneView.backgroundColor = .clear
|
self.sceneView.backgroundColor = .clear
|
||||||
if let url = getAppBundle().url(forResource: "lightspeed", withExtension: "scn") {
|
if let scene = loadCompressedScene(name: "lightspeed", version: sceneVersion) {
|
||||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
self.sceneView.scene = scene
|
||||||
}
|
}
|
||||||
self.sceneView.isUserInteractionEnabled = false
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
self.sceneView.preferredFramesPerSecond = 60
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import MergedAvatarsNode
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
private let sceneVersion: Int = 3
|
private let sceneVersion: Int = 1
|
||||||
|
|
||||||
final class GiftAvatarComponent: Component {
|
final class GiftAvatarComponent: Component {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -106,7 +106,7 @@ final class GiftAvatarComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setup() {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -371,6 +371,7 @@ final class PhoneDemoComponent: Component {
|
|||||||
case emoji
|
case emoji
|
||||||
case hello
|
case hello
|
||||||
case tag
|
case tag
|
||||||
|
case business
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Model {
|
enum Model {
|
||||||
@ -547,6 +548,13 @@ final class PhoneDemoComponent: Component {
|
|||||||
self.decorationView = starsView
|
self.decorationView = starsView
|
||||||
self.decorationContainerView.addSubview(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)
|
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)
|
||||||
|
|||||||
@ -698,7 +698,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
isCurrent = mode == .current
|
isCurrent = mode == .current
|
||||||
}
|
}
|
||||||
case .features:
|
case .features:
|
||||||
textString = strings.GroupBoost_AdditionalFeaturesText
|
textString = isGroup ? strings.GroupBoost_AdditionalFeaturesText : strings.ChannelBoost_AdditionalFeaturesText
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultTitle = strings.ChannelBoost_Level("\(level)").string
|
let defaultTitle = strings.ChannelBoost_Level("\(level)").string
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import GZip
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
|
||||||
private let sceneVersion: Int = 1
|
private let sceneVersion: Int = 2
|
||||||
|
|
||||||
private func deg2rad(_ number: Float) -> Float {
|
private func deg2rad(_ number: Float) -> Float {
|
||||||
return number * .pi / 180
|
return number * .pi / 180
|
||||||
@ -223,24 +223,7 @@ class PremiumCoinComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setup() {
|
private func setup() {
|
||||||
let resourceUrl: URL
|
guard let scene = loadCompressedScene(name: "coin", version: sceneVersion) else {
|
||||||
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 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,8 +299,8 @@ class PremiumCoinComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let fromScale: Float = 0.85
|
let fromScale: Float = 0.9
|
||||||
let toScale: Float = 0.9
|
let toScale: Float = 1.0
|
||||||
|
|
||||||
let animation = CABasicAnimation(keyPath: "scale")
|
let animation = CABasicAnimation(keyPath: "scale")
|
||||||
animation.duration = 2.0
|
animation.duration = 2.0
|
||||||
|
|||||||
@ -1177,7 +1177,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
text = strings.Premium_LastSeenInfo
|
text = strings.Premium_LastSeenInfo
|
||||||
case .messagePrivacy:
|
case .messagePrivacy:
|
||||||
text = strings.Premium_MessagePrivacyInfo
|
text = strings.Premium_MessagePrivacyInfo
|
||||||
case .doubleLimits, .stories, .business:
|
default:
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1392,6 +1392,14 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
|||||||
case lastSeen
|
case lastSeen
|
||||||
case messagePrivacy
|
case messagePrivacy
|
||||||
case business
|
case business
|
||||||
|
case folderTags
|
||||||
|
|
||||||
|
case businessLocation
|
||||||
|
case businessHours
|
||||||
|
case businessGreetingMessage
|
||||||
|
case businessQuickReplies
|
||||||
|
case businessAwayMessage
|
||||||
|
case businessChatBots
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Source: Equatable {
|
public enum Source: Equatable {
|
||||||
|
|||||||
@ -533,6 +533,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||||||
demoSubject = .messagePrivacy
|
demoSubject = .messagePrivacy
|
||||||
case .business:
|
case .business:
|
||||||
demoSubject = .business
|
demoSubject = .business
|
||||||
|
default:
|
||||||
|
demoSubject = .doubleLimits
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttonText: String
|
let buttonText: String
|
||||||
|
|||||||
@ -439,6 +439,15 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
case lastSeen
|
case lastSeen
|
||||||
case messagePrivacy
|
case messagePrivacy
|
||||||
case business
|
case business
|
||||||
|
case folderTags
|
||||||
|
|
||||||
|
case businessLocation
|
||||||
|
case businessHours
|
||||||
|
case businessGreetingMessage
|
||||||
|
case businessQuickReplies
|
||||||
|
case businessAwayMessage
|
||||||
|
case businessChatBots
|
||||||
|
|
||||||
|
|
||||||
public static var allCases: [PremiumPerk] {
|
public static var allCases: [PremiumPerk] {
|
||||||
return [
|
return [
|
||||||
@ -520,6 +529,8 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return "message_privacy"
|
return "message_privacy"
|
||||||
case .business:
|
case .business:
|
||||||
return "business"
|
return "business"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,6 +578,8 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return strings.Premium_MessagePrivacy
|
return strings.Premium_MessagePrivacy
|
||||||
case .business:
|
case .business:
|
||||||
return strings.Premium_Business
|
return strings.Premium_Business
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,6 +627,8 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return strings.Premium_MessagePrivacyInfo
|
return strings.Premium_MessagePrivacyInfo
|
||||||
case .business:
|
case .business:
|
||||||
return strings.Premium_BusinessInfo
|
return strings.Premium_BusinessInfo
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,6 +676,8 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return "Premium/Perk/MessagePrivacy"
|
return "Premium/Perk/MessagePrivacy"
|
||||||
case .business:
|
case .business:
|
||||||
return "Premium/Perk/Business"
|
return "Premium/Perk/Business"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1996,6 +2013,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||||
case .business:
|
case .business:
|
||||||
demoSubject = .business
|
demoSubject = .business
|
||||||
|
default:
|
||||||
|
demoSubject = .doubleLimits
|
||||||
}
|
}
|
||||||
|
|
||||||
let isPremium = state?.isPremium == true
|
let isPremium = state?.isPremium == true
|
||||||
@ -2100,7 +2119,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
foregroundColor: .white,
|
foregroundColor: .white,
|
||||||
iconName: perk.iconName
|
iconName: perk.iconName
|
||||||
))),
|
))),
|
||||||
action: { _ in
|
action: { [weak state] _ in
|
||||||
|
let isPremium = state?.isPremium == true
|
||||||
|
if isPremium {
|
||||||
switch perk {
|
switch perk {
|
||||||
case .location:
|
case .location:
|
||||||
let _ = (accountContext.engine.data.get(
|
let _ = (accountContext.engine.data.get(
|
||||||
@ -2138,6 +2159,39 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
case .chatbots:
|
case .chatbots:
|
||||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext))
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.disposed = {
|
||||||
|
updateIsFocused(false)
|
||||||
|
}
|
||||||
|
present(controller)
|
||||||
|
dismissImpl = { [weak controller] in
|
||||||
|
controller?.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
updateIsFocused(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
))))
|
))))
|
||||||
i += 1
|
i += 1
|
||||||
@ -2238,7 +2292,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
iconName: "Premium/BusinessPerk/Tag"
|
iconName: "Premium/BusinessPerk/Tag"
|
||||||
))),
|
))),
|
||||||
action: { _ in
|
action: { _ in
|
||||||
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, dismissed: nil))
|
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil))
|
||||||
}
|
}
|
||||||
))))
|
))))
|
||||||
|
|
||||||
|
|||||||
@ -834,6 +834,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 {
|
if let order = controller.order {
|
||||||
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
|
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
|
||||||
let initialIndex: Int
|
let initialIndex: Int
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import GZip
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
|
||||||
private let sceneVersion: Int = 6
|
private let sceneVersion: Int = 7
|
||||||
|
|
||||||
private func deg2rad(_ number: Float) -> Float {
|
private func deg2rad(_ number: Float) -> Float {
|
||||||
return number * .pi / 180
|
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 isIntro: Bool
|
||||||
let isVisible: Bool
|
let isVisible: Bool
|
||||||
let hasIdleAnimations: Bool
|
let hasIdleAnimations: Bool
|
||||||
@ -251,24 +275,7 @@ class PremiumStarComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setup() {
|
private func setup() {
|
||||||
let resourceUrl: URL
|
guard let scene = loadCompressedScene(name: "star", version: sceneVersion) else {
|
||||||
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 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import Display
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
private let sceneVersion: Int = 1
|
||||||
|
|
||||||
final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
||||||
private let sceneView: SCNView
|
private let sceneView: SCNView
|
||||||
|
|
||||||
@ -13,8 +15,8 @@ final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
self.sceneView.backgroundColor = .clear
|
self.sceneView.backgroundColor = .clear
|
||||||
if let url = getAppBundle().url(forResource: "swirl", withExtension: "scn") {
|
if let scene = loadCompressedScene(name: "swirl", version: sceneVersion) {
|
||||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
self.sceneView.scene = scene
|
||||||
}
|
}
|
||||||
self.sceneView.isUserInteractionEnabled = false
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
self.sceneView.preferredFramesPerSecond = 60
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
|
|||||||
@ -16,6 +16,8 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
public let text: String
|
public let text: String
|
||||||
public let actionTitle: String?
|
public let actionTitle: String?
|
||||||
public let action: () -> Void
|
public let action: () -> Void
|
||||||
|
public let additionalActionTitle: String?
|
||||||
|
public let additionalAction: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -24,7 +26,9 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
title: String,
|
title: String,
|
||||||
text: String,
|
text: String,
|
||||||
actionTitle: String?,
|
actionTitle: String?,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void,
|
||||||
|
additionalActionTitle: String?,
|
||||||
|
additionalAction: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -33,6 +37,8 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
self.text = text
|
self.text = text
|
||||||
self.actionTitle = actionTitle
|
self.actionTitle = actionTitle
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.additionalActionTitle = additionalActionTitle
|
||||||
|
self.additionalAction = additionalAction
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool {
|
public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool {
|
||||||
@ -54,6 +60,9 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
if lhs.actionTitle != rhs.actionTitle {
|
if lhs.actionTitle != rhs.actionTitle {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.additionalActionTitle != rhs.additionalActionTitle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +74,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let text = ComponentView<Empty>()
|
private let text = ComponentView<Empty>()
|
||||||
private var button: ComponentView<Empty>?
|
private var button: ComponentView<Empty>?
|
||||||
|
private var additionalButton: ComponentView<Empty>?
|
||||||
|
|
||||||
override public init(frame: CGRect) {
|
override public init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -139,7 +149,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 240.0, height: 50.0)
|
containerSize: CGSize(width: 260.0, height: 50.0)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if let button = self.button {
|
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 animationSpacing: CGFloat = 11.0
|
||||||
let titleSpacing: CGFloat = 17.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
|
var totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height
|
||||||
if let buttonSize {
|
if let buttonSize {
|
||||||
totalHeight += buttonSpacing + buttonSize.height
|
totalHeight += buttonSpacing + buttonSize.height
|
||||||
}
|
}
|
||||||
|
if let additionalButtonSize {
|
||||||
|
totalHeight += buttonSpacing + additionalButtonSize.height
|
||||||
|
}
|
||||||
|
|
||||||
var contentY = floor((availableSize.height - totalHeight) * 0.5)
|
var contentY = floor((availableSize.height - totalHeight) * 0.5)
|
||||||
|
|
||||||
@ -185,7 +233,14 @@ public final class EmptyStateIndicatorComponent: Component {
|
|||||||
self.addSubview(buttonView)
|
self.addSubview(buttonView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: contentY), size: buttonSize))
|
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
|
return availableSize
|
||||||
|
|||||||
@ -178,7 +178,11 @@ public final class ListActionItemComponent: Component {
|
|||||||
let contentRightInset: CGFloat
|
let contentRightInset: CGFloat
|
||||||
switch component.accessory {
|
switch component.accessory {
|
||||||
case .none:
|
case .none:
|
||||||
|
if let _ = component.icon {
|
||||||
|
contentRightInset = 42.0
|
||||||
|
} else {
|
||||||
contentRightInset = 16.0
|
contentRightInset = 16.0
|
||||||
|
}
|
||||||
case .arrow:
|
case .arrow:
|
||||||
contentRightInset = 30.0
|
contentRightInset = 30.0
|
||||||
case .toggle:
|
case .toggle:
|
||||||
@ -189,7 +193,7 @@ public final class ListActionItemComponent: Component {
|
|||||||
contentHeight += verticalInset
|
contentHeight += verticalInset
|
||||||
|
|
||||||
if component.leftIcon != nil {
|
if component.leftIcon != nil {
|
||||||
contentLeftInset += 46.0
|
contentLeftInset += 52.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
@ -239,7 +243,7 @@ public final class ListActionItemComponent: Component {
|
|||||||
|
|
||||||
var iconOffset: CGFloat = 0.0
|
var iconOffset: CGFloat = 0.0
|
||||||
if case .none = component.accessory {
|
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)
|
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:
|
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)
|
push(controller)
|
||||||
case .notificationsAndSounds:
|
case .notificationsAndSounds:
|
||||||
if let settings = self.data?.globalSettings {
|
if let settings = self.data?.globalSettings {
|
||||||
|
|||||||
@ -295,7 +295,23 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
let _ = paneNode.scrollToTop()
|
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 {
|
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.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
@ -313,7 +329,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
|
|
||||||
var bottomInset: CGFloat = environment.safeInsets.bottom
|
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>
|
let selectionPanel: ComponentView<Empty>
|
||||||
var selectionPanelTransition = transition
|
var selectionPanelTransition = transition
|
||||||
if let current = self.selectionPanel {
|
if let current = self.selectionPanel {
|
||||||
@ -327,7 +343,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
let buttonText: String
|
let buttonText: String
|
||||||
switch component.scope {
|
switch component.scope {
|
||||||
case .saved:
|
case .saved:
|
||||||
buttonText = environment.strings.ChatList_Context_Archive
|
buttonText = self.selectedCount > 0 ? environment.strings.ChatList_Context_Archive : environment.strings.StoryList_SavedAddAction
|
||||||
case .archive:
|
case .archive:
|
||||||
buttonText = environment.strings.StoryList_SaveToProfile
|
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 {
|
guard let self, let component = self.component, let environment = self.environment else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let paneNode = self.paneNode, !paneNode.selectedIds.isEmpty else {
|
guard let paneNode = self.paneNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,6 +377,9 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
switch component.scope {
|
switch component.scope {
|
||||||
case .saved:
|
case .saved:
|
||||||
let selectedCount = paneNode.selectedItems.count
|
let selectedCount = paneNode.selectedItems.count
|
||||||
|
if selectedCount == 0 {
|
||||||
|
self.openCreateStory()
|
||||||
|
} else {
|
||||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
|
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
|
||||||
|
|
||||||
paneNode.setIsSelectionModeActive(false)
|
paneNode.setIsSelectionModeActive(false)
|
||||||
@ -376,6 +395,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
), in: .current)
|
), in: .current)
|
||||||
|
}
|
||||||
case .archive:
|
case .archive:
|
||||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start()
|
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
|
listContext: nil
|
||||||
)
|
)
|
||||||
|
paneNode.isEmptyUpdated = { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
self.paneNode = paneNode
|
self.paneNode = paneNode
|
||||||
|
if let selectionPanelView = self.selectionPanel?.view {
|
||||||
|
self.insertSubview(paneNode.view, belowSubview: selectionPanelView)
|
||||||
|
} else {
|
||||||
self.addSubview(paneNode.view)
|
self.addSubview(paneNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
paneNode.emptyAction = { [weak self] in
|
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 {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -948,6 +948,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var isEmptyUpdated: (Bool) -> Void = { _ in }
|
||||||
|
|
||||||
public private(set) var isSelectionModeActive: Bool
|
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)?
|
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 openCurrentDate: (() -> Void)?
|
||||||
public var paneDidScroll: (() -> Void)?
|
public var paneDidScroll: (() -> Void)?
|
||||||
public var emptyAction: (() -> Void)?
|
public var emptyAction: (() -> Void)?
|
||||||
|
public var additionalEmptyAction: (() -> Void)?
|
||||||
|
|
||||||
public var ensureRectVisible: ((UIView, CGRect) -> 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) {
|
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) {
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.isEmptyUpdated(self.isEmpty)
|
||||||
|
|
||||||
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
||||||
var gridSnapshot: UIView?
|
var gridSnapshot: UIView?
|
||||||
@ -2027,14 +2031,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
context: self.context,
|
context: self.context,
|
||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
animationName: "StoryListEmpty",
|
animationName: "StoryListEmpty",
|
||||||
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title,
|
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
|
||||||
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text,
|
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
|
||||||
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedAddAction,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emptyAction?()
|
self.emptyAction?()
|
||||||
|
},
|
||||||
|
additionalActionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||||
|
additionalAction: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.additionalEmptyAction?()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
|||||||
@ -134,6 +134,9 @@ final class StoryAuthorInfoComponent: Component {
|
|||||||
if timeString.count < 6 {
|
if timeString.count < 6 {
|
||||||
combinedString.append(NSAttributedString(string: " • \(timeString)", font: Font.regular(11.0), textColor: subtitleColor))
|
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
|
subtitle = combinedString
|
||||||
subtitleTruncationType = .middle
|
subtitleTruncationType = .middle
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1882,8 +1882,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return archiveSettingsController(context: context)
|
return archiveSettingsController(context: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController {
|
public func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController {
|
||||||
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, dismissed: dismissed)
|
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, scrollToTags: scrollToTags, dismissed: dismissed)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeBusinessSetupScreen(context: AccountContext) -> ViewController {
|
public func makeBusinessSetupScreen(context: AccountContext) -> ViewController {
|
||||||
@ -2040,6 +2040,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
mappedSubject = .lastSeen
|
mappedSubject = .lastSeen
|
||||||
case .messagePrivacy:
|
case .messagePrivacy:
|
||||||
mappedSubject = .messagePrivacy
|
mappedSubject = .messagePrivacy
|
||||||
|
default:
|
||||||
|
mappedSubject = .doubleLimits
|
||||||
}
|
}
|
||||||
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
|
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user