mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
11aa9be45c
commit
8b1bd1dec0
74 changed files with 5570 additions and 805 deletions
|
|
@ -11347,6 +11347,9 @@ Sorry for the inconvenience.";
|
|||
"Premium.Business.Away.Title" = "Away Messages";
|
||||
"Premium.Business.Away.Text" = "Define messages that are automatically sent when you are off.";
|
||||
|
||||
"Premium.Business.Intro.Title" = "Intro";
|
||||
"Premium.Business.Intro.Text" = "Customize the message people see before they start a chat with you.";
|
||||
|
||||
"Premium.Business.Chatbots.Title" = "Chatbots";
|
||||
"Premium.Business.Chatbots.Text" = "Add any third party chatbots that will process customer interactions.";
|
||||
|
||||
|
|
@ -11360,6 +11363,7 @@ Sorry for the inconvenience.";
|
|||
"Business.GreetingMessages" = "Greeting Messages";
|
||||
"Business.AwayMessages" = "Away Messages";
|
||||
"Business.Chatbots" = "Chatbots";
|
||||
"Business.Intro" = "Intro";
|
||||
|
||||
"Business.LocationInfo" = "Display the location of your business on your account.";
|
||||
"Business.OpeningHoursInfo" = "Show to your customers when you are open for business.";
|
||||
|
|
@ -11367,6 +11371,7 @@ Sorry for the inconvenience.";
|
|||
"Business.GreetingMessagesInfo" = "Create greetings that will be automatically sent to new customers.";
|
||||
"Business.AwayMessagesInfo" = "Define messages that are automatically sent when you are off.";
|
||||
"Business.ChatbotsInfo" = "Add any third-party chatbots that will process customer interactions.";
|
||||
"Business.IntroInfo" = "Customize the message people see before they start a chat with you.";
|
||||
|
||||
"Business.MoreFeaturesTitle" = "MORE BUSINESS FEATURES";
|
||||
"Business.MoreFeaturesInfo" = "Check this section later for new business features.";
|
||||
|
|
@ -11606,3 +11611,8 @@ Sorry for the inconvenience.";
|
|||
"CollectibleItemInfo.ShareInlineText.LearnMore" = "Learn more >";
|
||||
|
||||
"Stickers.Edit" = "EDIT";
|
||||
|
||||
"ChannelBoost.Table.NoAds" = "Switch Off Ads";
|
||||
|
||||
"ChannelBoost.NoAds" = "Switch Off Ads";
|
||||
"ChannelBoost.EnableNoAdsLevelText" = "Your channel needs **Level %1$@** to switch off ads.";
|
||||
|
|
|
|||
|
|
@ -991,17 +991,19 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
|
||||
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (() -> Void)?) -> ViewController
|
||||
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
|
||||
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
||||
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
||||
|
||||
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
|
||||
|
||||
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
|
||||
|
||||
// func makeStickerEditorScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, initialSticker: TelegramMediaFile?, targetStickerPack: StickerPackReference?) -> ViewController
|
||||
func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController
|
||||
|
||||
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
||||
func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController
|
||||
|
||||
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
|
||||
|
||||
func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController
|
||||
|
|
|
|||
|
|
@ -35,3 +35,11 @@ public final class GalleryControllerActionInteraction {
|
|||
self.updateCanReadHistory = updateCanReadHistory
|
||||
}
|
||||
}
|
||||
|
||||
public protocol StickerPackScreen {
|
||||
|
||||
}
|
||||
|
||||
public protocol StickerPickerInput {
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,21 @@ public enum PremiumPrivacySubject {
|
|||
case readTime
|
||||
}
|
||||
|
||||
public enum BoostSubject: Equatable {
|
||||
case stories
|
||||
case channelReactions(reactionCount: Int32)
|
||||
case nameColors(colors: PeerNameColor)
|
||||
case nameIcon
|
||||
case profileColors(colors: PeerNameColor)
|
||||
case profileIcon
|
||||
case emojiStatus
|
||||
case wallpaper
|
||||
case customWallpaper
|
||||
case audioTranscription
|
||||
case emojiPack
|
||||
case noAds
|
||||
}
|
||||
|
||||
public struct PremiumConfiguration {
|
||||
public static var defaultValue: PremiumConfiguration {
|
||||
return PremiumConfiguration(
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ swift_library(
|
|||
"//submodules/Camera",
|
||||
"//submodules/TelegramUI/Components/DustEffect",
|
||||
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
||||
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import EntityKeyboard
|
|||
import TelegramUIPreferences
|
||||
import FastBlur
|
||||
import MediaEditor
|
||||
import StickerPickerScreen
|
||||
|
||||
public struct DrawingResultData {
|
||||
public let data: Data?
|
||||
|
|
@ -493,7 +494,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
|
||||
let context: AccountContext
|
||||
let sourceHint: DrawingScreen.SourceHint?
|
||||
let existingStickerPickerInputData: Promise<StickerPickerInputData>?
|
||||
let existingStickerPickerInputData: Promise<StickerPickerInput>?
|
||||
let isVideo: Bool
|
||||
let isAvatar: Bool
|
||||
let isInteractingWithEntities: Bool
|
||||
|
|
@ -529,7 +530,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
init(
|
||||
context: AccountContext,
|
||||
sourceHint: DrawingScreen.SourceHint?,
|
||||
existingStickerPickerInputData: Promise<StickerPickerInputData>?,
|
||||
existingStickerPickerInputData: Promise<StickerPickerInput>?,
|
||||
isVideo: Bool,
|
||||
isAvatar: Bool,
|
||||
isInteractingWithEntities: Bool,
|
||||
|
|
@ -682,11 +683,11 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
|
||||
var lastSize: CGFloat = 0.5
|
||||
|
||||
private let stickerPickerInputData: Promise<StickerPickerInputData>
|
||||
private let stickerPickerInputData: Promise<StickerPickerInput>
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
existingStickerPickerInputData: Promise<StickerPickerInputData>?,
|
||||
existingStickerPickerInputData: Promise<StickerPickerInput>?,
|
||||
updateToolState: ActionSlot<DrawingToolState>,
|
||||
insertEntity: ActionSlot<DrawingEntity>,
|
||||
deselectEntity: ActionSlot<Void>,
|
||||
|
|
@ -728,7 +729,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
if let existingStickerPickerInputData {
|
||||
self.stickerPickerInputData = existingStickerPickerInputData
|
||||
} else {
|
||||
self.stickerPickerInputData = Promise<StickerPickerInputData>()
|
||||
self.stickerPickerInputData = Promise<StickerPickerInput>()
|
||||
|
||||
let stickerPickerInputData = self.stickerPickerInputData
|
||||
Queue.concurrentDefaultQueue().after(0.5, {
|
||||
|
|
@ -762,7 +763,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
let signal = combineLatest(queue: .mainQueue(),
|
||||
emojiItems,
|
||||
stickerItems
|
||||
) |> map { emoji, stickers -> StickerPickerInputData in
|
||||
) |> map { emoji, stickers -> StickerPickerInput in
|
||||
return StickerPickerInputData(emoji: emoji, stickers: stickers, gifs: nil)
|
||||
}
|
||||
|
||||
|
|
@ -1012,7 +1013,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||
self.currentMode = .sticker
|
||||
|
||||
self.updateEntitiesPlayback.invoke(false)
|
||||
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), hasInteractiveStickers: false)
|
||||
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), forceDark: true, hasInteractiveStickers: false)
|
||||
if let presentGallery = self.presentGallery {
|
||||
controller.presentGallery = presentGallery
|
||||
}
|
||||
|
|
@ -2753,7 +2754,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||
private let externalDrawingView: DrawingView?
|
||||
private let externalEntitiesView: DrawingEntitiesView?
|
||||
private let externalSelectionContainerView: DrawingSelectionContainerView?
|
||||
private let existingStickerPickerInputData: Promise<StickerPickerInputData>?
|
||||
private let existingStickerPickerInputData: Promise<StickerPickerInput>?
|
||||
|
||||
public var requestDismiss: () -> Void = {}
|
||||
public var requestApply: () -> Void = {}
|
||||
|
|
@ -2762,7 +2763,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||
|
||||
public var presentGallery: (() -> Void)?
|
||||
|
||||
public init(context: AccountContext, sourceHint: SourceHint? = nil, size: CGSize, originalSize: CGSize, isVideo: Bool, isAvatar: Bool, drawingView: DrawingView?, entitiesView: (UIView & TGPhotoDrawingEntitiesView)?, selectionContainerView: DrawingSelectionContainerView?, existingStickerPickerInputData: Promise<StickerPickerInputData>? = nil) {
|
||||
public init(context: AccountContext, sourceHint: SourceHint? = nil, size: CGSize, originalSize: CGSize, isVideo: Bool, isAvatar: Bool, drawingView: DrawingView?, entitiesView: (UIView & TGPhotoDrawingEntitiesView)?, selectionContainerView: DrawingSelectionContainerView?, existingStickerPickerInputData: Promise<StickerPickerInput>? = nil) {
|
||||
self.context = context
|
||||
self.sourceHint = sourceHint
|
||||
self.size = size
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ swift_library(
|
|||
"//submodules/TelegramCore",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ public enum ItemListControllerTitle: Equatable {
|
|||
case text(String)
|
||||
case textWithSubtitle(String, String)
|
||||
case sectionControl([String], Int)
|
||||
case textWithTabs(String, [String], Int)
|
||||
}
|
||||
|
||||
public final class ItemListControllerTabBarItem: Equatable {
|
||||
|
|
@ -113,6 +114,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
|||
private var tabBarItemInfo: ItemListControllerTabBarItem?
|
||||
private var navigationButtonActions: (left: (() -> Void)?, right: (() -> Void)?, secondaryRight: (() -> Void)?) = (nil, nil, nil)
|
||||
private var segmentedTitleView: ItemListControllerSegmentedTitleView?
|
||||
private var tabsNavigationContentNode: ItemListControllerTabsContentNode?
|
||||
|
||||
private var presentationData: ItemListPresentationData
|
||||
|
||||
|
|
@ -318,10 +320,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
|||
strongSelf.title = text
|
||||
strongSelf.navigationItem.titleView = nil
|
||||
strongSelf.segmentedTitleView = nil
|
||||
strongSelf.navigationBar?.setContentNode(nil, animated: false)
|
||||
case let .textWithSubtitle(title, subtitle):
|
||||
strongSelf.title = ""
|
||||
strongSelf.navigationItem.titleView = ItemListTextWithSubtitleTitleView(theme: controllerState.presentationData.theme, title: title, subtitle: subtitle)
|
||||
strongSelf.segmentedTitleView = nil
|
||||
strongSelf.navigationBar?.setContentNode(nil, animated: false)
|
||||
case let .sectionControl(sections, index):
|
||||
strongSelf.title = ""
|
||||
if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections {
|
||||
|
|
@ -336,6 +340,21 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
|||
}
|
||||
}
|
||||
}
|
||||
strongSelf.navigationBar?.setContentNode(nil, animated: false)
|
||||
case let .textWithTabs(title, sections, index):
|
||||
strongSelf.title = title
|
||||
if let tabsNavigationContentNode = strongSelf.tabsNavigationContentNode, tabsNavigationContentNode.segments == sections {
|
||||
tabsNavigationContentNode.index = index
|
||||
} else {
|
||||
let tabsNavigationContentNode = ItemListControllerTabsContentNode(theme: controllerState.presentationData.theme, segments: sections, selectedIndex: index)
|
||||
strongSelf.tabsNavigationContentNode = tabsNavigationContentNode
|
||||
strongSelf.navigationBar?.setContentNode(tabsNavigationContentNode, animated: false)
|
||||
tabsNavigationContentNode.indexUpdated = { index in
|
||||
if let strongSelf = self {
|
||||
strongSelf.titleControlValueChanged?(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.navigationButtonActions = (left: controllerState.leftNavigationButton?.action, right: controllerState.rightNavigationButton?.action, secondaryRight: controllerState.secondaryRightNavigationButton?.action)
|
||||
|
|
|
|||
|
|
@ -361,14 +361,18 @@ open class ItemListControllerNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var inVoiceOver = false
|
||||
if let validLayout = self?.validLayout {
|
||||
inVoiceOver = validLayout.0.inVoiceOver
|
||||
}
|
||||
strongSelf.contentOffsetChanged?(offset, inVoiceOver)
|
||||
|
||||
self?.contentOffsetChanged?(offset, inVoiceOver)
|
||||
|
||||
if let strongSelf = self {
|
||||
if let navigationContentNode = strongSelf.navigationBar.contentNode, case .expansion = navigationContentNode.mode {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
} else {
|
||||
var previousContentOffsetValue: CGFloat?
|
||||
if let previousContentOffset = strongSelf.previousContentOffset {
|
||||
if case let .known(value) = previousContentOffset {
|
||||
|
|
@ -378,30 +382,30 @@ open class ItemListControllerNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
switch offset {
|
||||
case let .known(value):
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue >= 30.0 {
|
||||
transition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(value, transition: transition)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||
}
|
||||
case .unknown, .none:
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(1000.0, transition: .immediate)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
}
|
||||
case let .known(value):
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue >= 30.0 {
|
||||
transition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(value, transition: transition)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||
}
|
||||
case .unknown, .none:
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(1000.0, transition: .immediate)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.previousContentOffset = offset
|
||||
}
|
||||
|
||||
strongSelf.previousContentOffset = offset
|
||||
}
|
||||
|
||||
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import SegmentedControlNode
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import TabSelectorComponent
|
||||
import Display
|
||||
|
||||
public final class ItemListControllerSegmentedTitleView: UIView {
|
||||
private let tabSelector = ComponentView<Empty>()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import TabSelectorComponent
|
||||
|
||||
private let searchBarFont = Font.regular(17.0)
|
||||
|
||||
final class ItemListControllerTabsContentNode: NavigationBarContentNode {
|
||||
private let tabSelector = ComponentView<Empty>()
|
||||
|
||||
var theme: PresentationTheme {
|
||||
didSet {
|
||||
if self.theme !== oldValue {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
var segments: [String] {
|
||||
didSet {
|
||||
if self.segments != oldValue {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var index: Int {
|
||||
didSet {
|
||||
if self.index != oldValue {
|
||||
self.update(transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indexUpdated: ((Int) -> Void)?
|
||||
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||
|
||||
init(theme: PresentationTheme, segments: [String], selectedIndex: Int) {
|
||||
self.theme = theme
|
||||
self.segments = segments
|
||||
self.index = selectedIndex
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
}
|
||||
|
||||
private func update(transition: ContainedViewLayoutTransition = .immediate) {
|
||||
guard let (size, leftInset, rightInset) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition)
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let isFirstTime = self.validLayout == nil
|
||||
self.validLayout = (size, leftInset, rightInset)
|
||||
|
||||
let mappedItems = zip(0 ..< self.segments.count, self.segments).map { index, segment in
|
||||
return TabSelectorComponent.Item(
|
||||
id: AnyHashable(index),
|
||||
title: segment
|
||||
)
|
||||
}
|
||||
|
||||
let tabSelectorSize = self.tabSelector.update(
|
||||
transition: Transition(transition),
|
||||
component: AnyComponent(TabSelectorComponent(
|
||||
colors: TabSelectorComponent.Colors(
|
||||
foreground: self.theme.list.itemSecondaryTextColor,
|
||||
selection: self.theme.list.itemAccentColor
|
||||
),
|
||||
customLayout: TabSelectorComponent.CustomLayout(
|
||||
font: Font.medium(14.0),
|
||||
spacing: 24.0,
|
||||
lineSelection: true
|
||||
),
|
||||
items: mappedItems,
|
||||
selectedId: AnyHashable(self.index),
|
||||
setSelectedId: { [weak self] id in
|
||||
guard let self, let index = id.base as? Int else {
|
||||
return
|
||||
}
|
||||
self.indexUpdated?(index)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width, height: 44.0)
|
||||
)
|
||||
let tabSelectorFrame = CGRect(origin: CGPoint(x: floor((size.width - tabSelectorSize.width) / 2.0), y: floor((size.height - tabSelectorSize.height) / 2.0) + 4.0), size: tabSelectorSize)
|
||||
if let tabSelectorView = self.tabSelector.view {
|
||||
if tabSelectorView.superview == nil {
|
||||
self.view.addSubview(tabSelectorView)
|
||||
}
|
||||
transition.updateFrame(view: tabSelectorView, frame: tabSelectorFrame)
|
||||
}
|
||||
|
||||
if isFirstTime {
|
||||
self.requestContainerLayout(.immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override var height: CGFloat {
|
||||
return self.nominalHeight
|
||||
}
|
||||
|
||||
override var clippedHeight: CGFloat {
|
||||
return self.nominalHeight
|
||||
}
|
||||
|
||||
override var nominalHeight: CGFloat {
|
||||
return 54.0// + self.additionalHeight
|
||||
}
|
||||
|
||||
override var mode: NavigationBarContentMode {
|
||||
return .expansion
|
||||
}
|
||||
}
|
||||
|
|
@ -39,19 +39,28 @@ public enum ItemListDisclosureLabelStyle {
|
|||
case image(image: UIImage, size: CGSize)
|
||||
}
|
||||
|
||||
public enum ItemListDisclosureItemDetailLabelColor {
|
||||
case generic
|
||||
case constructive
|
||||
case destructive
|
||||
}
|
||||
|
||||
public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
let presentationData: ItemListPresentationData
|
||||
let icon: UIImage?
|
||||
let context: AccountContext?
|
||||
let iconPeer: EnginePeer?
|
||||
let title: String
|
||||
let attributedTitle: NSAttributedString?
|
||||
let titleColor: ItemListDisclosureItemTitleColor
|
||||
let titleFont: ItemListDisclosureItemTitleFont
|
||||
let titleIcon: UIImage?
|
||||
let enabled: Bool
|
||||
let label: String
|
||||
let attributedLabel: NSAttributedString?
|
||||
let labelStyle: ItemListDisclosureLabelStyle
|
||||
let additionalDetailLabel: String?
|
||||
let additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor
|
||||
public let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let disclosureStyle: ItemListDisclosureStyle
|
||||
|
|
@ -60,19 +69,22 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
|||
public let tag: ItemListItemTag?
|
||||
public let shimmeringIndex: Int?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.context = context
|
||||
self.iconPeer = iconPeer
|
||||
self.title = title
|
||||
self.attributedTitle = attributedTitle
|
||||
self.titleColor = titleColor
|
||||
self.titleFont = titleFont
|
||||
self.titleIcon = titleIcon
|
||||
self.enabled = enabled
|
||||
self.labelStyle = labelStyle
|
||||
self.label = label
|
||||
self.attributedLabel = attributedLabel
|
||||
self.additionalDetailLabel = additionalDetailLabel
|
||||
self.additionalDetailLabelColor = additionalDetailLabelColor
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
self.disclosureStyle = disclosureStyle
|
||||
|
|
@ -358,7 +370,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
maxTitleWidth -= 12.0
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: item.attributedTitle ?? NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: item.attributedTitle != nil ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let detailFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||
|
||||
|
|
@ -392,7 +404,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
multilineLabel = true
|
||||
}
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: item.attributedLabel ?? NSAttributedString(string: item.label, font: labelFont, textColor: labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var additionalDetailLabelInfo: (TextNodeLayout, () -> TextNode)?
|
||||
if let additionalDetailLabel = item.additionalDetailLabel {
|
||||
|
|
@ -400,7 +412,16 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
if labelLayout.size.width != 0 {
|
||||
detailRightInset += labelLayout.size.width + 12.0
|
||||
}
|
||||
additionalDetailLabelInfo = makeAdditionalDetailLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: additionalDetailLabel, font: detailFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - detailRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let additionalDetailColor: UIColor
|
||||
switch item.additionalDetailLabelColor {
|
||||
case .generic:
|
||||
additionalDetailColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||
case .constructive:
|
||||
additionalDetailColor = item.presentationData.theme.list.itemDisclosureActions.constructive.fillColor
|
||||
case .destructive:
|
||||
additionalDetailColor = item.presentationData.theme.list.itemDestructiveColor
|
||||
}
|
||||
additionalDetailLabelInfo = makeAdditionalDetailLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: additionalDetailLabel, font: detailFont, textColor: additionalDetailColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - detailRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
}
|
||||
|
||||
let verticalInset: CGFloat
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||
"//submodules/TelegramUI/Components/PremiumPeerShortcutComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -301,14 +301,13 @@ private final class BusinessListComponent: CombinedComponent {
|
|||
let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
|
||||
let colors = [
|
||||
UIColor(rgb: 0x007aff),
|
||||
UIColor(rgb: 0x798aff),
|
||||
UIColor(rgb: 0xac64f3),
|
||||
UIColor(rgb: 0xc456ae),
|
||||
UIColor(rgb: 0xe95d44),
|
||||
UIColor(rgb: 0xf2822a),
|
||||
UIColor(rgb: 0xe79519),
|
||||
UIColor(rgb: 0xe7ad19)
|
||||
UIColor(rgb: 0xef6922),
|
||||
UIColor(rgb: 0xe54937),
|
||||
UIColor(rgb: 0xdb374b),
|
||||
UIColor(rgb: 0xbc4395),
|
||||
UIColor(rgb: 0x9b4fed),
|
||||
UIColor(rgb: 0x8958ff),
|
||||
UIColor(rgb: 0x676bff)
|
||||
]
|
||||
|
||||
let titleColor = theme.list.itemPrimaryTextColor
|
||||
|
|
@ -397,6 +396,20 @@ private final class BusinessListComponent: CombinedComponent {
|
|||
)
|
||||
)
|
||||
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "intro",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: strings.Premium_Business_Intro_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Premium_Business_Intro_Text,
|
||||
textColor: textColor,
|
||||
iconName: "Premium/Business/Intro",
|
||||
iconColor: colors[5]
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "chatbots",
|
||||
|
|
@ -406,7 +419,7 @@ private final class BusinessListComponent: CombinedComponent {
|
|||
text: strings.Premium_Business_Chatbots_Text,
|
||||
textColor: textColor,
|
||||
iconName: "Premium/Business/Chatbots",
|
||||
iconColor: colors[5]
|
||||
iconColor: colors[6]
|
||||
))
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import SolidRoundedButtonComponent
|
|||
import BlurredBackgroundComponent
|
||||
import UndoUI
|
||||
import ConfettiEffect
|
||||
import PremiumPeerShortcutComponent
|
||||
|
||||
func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 {
|
||||
switch subject {
|
||||
|
|
@ -55,22 +56,12 @@ func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: Acco
|
|||
return configuration.minGroupAudioTranscriptionLevel
|
||||
case .emojiPack:
|
||||
return configuration.minGroupEmojiPackLevel
|
||||
case .noAds:
|
||||
return 30
|
||||
}
|
||||
}
|
||||
|
||||
public enum BoostSubject: Equatable {
|
||||
case stories
|
||||
case channelReactions(reactionCount: Int32)
|
||||
case nameColors(colors: PeerNameColor)
|
||||
case nameIcon
|
||||
case profileColors(colors: PeerNameColor)
|
||||
case profileIcon
|
||||
case emojiStatus
|
||||
case wallpaper
|
||||
case customWallpaper
|
||||
case audioTranscription
|
||||
case emojiPack
|
||||
|
||||
extension BoostSubject {
|
||||
public func requiredLevel(group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 {
|
||||
return requiredBoostSubjectLevel(subject: self, group: group, context: context, configuration: configuration)
|
||||
}
|
||||
|
|
@ -247,6 +238,7 @@ private final class LevelSectionComponent: CombinedComponent {
|
|||
case customWallpaper
|
||||
case audioTranscription
|
||||
case emojiPack
|
||||
case noAds
|
||||
|
||||
func title(strings: PresentationStrings, isGroup: Bool) -> String {
|
||||
switch self {
|
||||
|
|
@ -274,6 +266,8 @@ private final class LevelSectionComponent: CombinedComponent {
|
|||
return strings.GroupBoost_Table_Group_VoiceToText
|
||||
case .emojiPack:
|
||||
return strings.GroupBoost_Table_Group_EmojiPack
|
||||
case .noAds:
|
||||
return strings.ChannelBoost_Table_NoAds
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +297,8 @@ private final class LevelSectionComponent: CombinedComponent {
|
|||
return "Premium/BoostPerk/AudioTranscription"
|
||||
case .emojiPack:
|
||||
return "Premium/BoostPerk/EmojiPack"
|
||||
case .noAds:
|
||||
return "Premium/BoostPerk/NoAds"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -639,6 +635,8 @@ private final class SheetContent: CombinedComponent {
|
|||
textString = ""
|
||||
case .emojiPack:
|
||||
textString = strings.GroupBoost_EnableEmojiPackLevelText("\(requiredLevel)").string
|
||||
case .noAds:
|
||||
textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string
|
||||
}
|
||||
} else {
|
||||
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
|
||||
|
|
@ -733,11 +731,10 @@ private final class SheetContent: CombinedComponent {
|
|||
let peerShortcut = peerShortcut.update(
|
||||
component: Button(
|
||||
content: AnyComponent(
|
||||
PeerShortcutComponent(
|
||||
PremiumPeerShortcutComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
peer: peer
|
||||
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
|
@ -1095,85 +1092,100 @@ private final class SheetContent: CombinedComponent {
|
|||
isFeatures = true
|
||||
}
|
||||
|
||||
if let nextLevels {
|
||||
for level in nextLevels {
|
||||
var perks: [LevelSectionComponent.Perk] = []
|
||||
|
||||
perks.append(.story(level))
|
||||
|
||||
if !isGroup {
|
||||
perks.append(.reaction(level))
|
||||
}
|
||||
|
||||
var nameColorsCount: Int32 = 0
|
||||
for (colorLevel, count) in nameColorsAtLevel {
|
||||
if level >= colorLevel && colorLevel == 1 {
|
||||
nameColorsCount = count
|
||||
}
|
||||
}
|
||||
if !isGroup && nameColorsCount > 0 {
|
||||
perks.append(.nameColor(nameColorsCount))
|
||||
}
|
||||
|
||||
var profileColorsCount: Int32 = 0
|
||||
for (colorLevel, count) in profileColorsAtLevel {
|
||||
if level >= colorLevel {
|
||||
profileColorsCount += count
|
||||
}
|
||||
}
|
||||
if profileColorsCount > 0 {
|
||||
perks.append(.profileColor(profileColorsCount))
|
||||
}
|
||||
|
||||
func layoutLevel(_ level: Int32) {
|
||||
var perks: [LevelSectionComponent.Perk] = []
|
||||
|
||||
if isGroup && level >= requiredBoostSubjectLevel(subject: .emojiPack, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.emojiPack)
|
||||
}
|
||||
perks.append(.story(level))
|
||||
|
||||
if level >= requiredBoostSubjectLevel(subject: .profileIcon, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.profileIcon)
|
||||
if !isGroup {
|
||||
perks.append(.reaction(level))
|
||||
}
|
||||
|
||||
var nameColorsCount: Int32 = 0
|
||||
for (colorLevel, count) in nameColorsAtLevel {
|
||||
if level >= colorLevel && colorLevel == 1 {
|
||||
nameColorsCount = count
|
||||
}
|
||||
|
||||
if isGroup && level >= requiredBoostSubjectLevel(subject: .audioTranscription, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.audioTranscription)
|
||||
}
|
||||
if !isGroup && nameColorsCount > 0 {
|
||||
perks.append(.nameColor(nameColorsCount))
|
||||
}
|
||||
|
||||
var profileColorsCount: Int32 = 0
|
||||
for (colorLevel, count) in profileColorsAtLevel {
|
||||
if level >= colorLevel {
|
||||
profileColorsCount += count
|
||||
}
|
||||
|
||||
var linkColorsCount: Int32 = 0
|
||||
for (colorLevel, count) in nameColorsAtLevel {
|
||||
if level >= colorLevel {
|
||||
linkColorsCount += count
|
||||
}
|
||||
}
|
||||
if profileColorsCount > 0 {
|
||||
perks.append(.profileColor(profileColorsCount))
|
||||
}
|
||||
|
||||
if isGroup && level >= requiredBoostSubjectLevel(subject: .emojiPack, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.emojiPack)
|
||||
}
|
||||
|
||||
if level >= requiredBoostSubjectLevel(subject: .profileIcon, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.profileIcon)
|
||||
}
|
||||
|
||||
if isGroup && level >= requiredBoostSubjectLevel(subject: .audioTranscription, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.audioTranscription)
|
||||
}
|
||||
|
||||
var linkColorsCount: Int32 = 0
|
||||
for (colorLevel, count) in nameColorsAtLevel {
|
||||
if level >= colorLevel {
|
||||
linkColorsCount += count
|
||||
}
|
||||
if !isGroup && linkColorsCount > 0 {
|
||||
perks.append(.linkColor(linkColorsCount))
|
||||
}
|
||||
|
||||
if !isGroup && level >= requiredBoostSubjectLevel(subject: .nameIcon, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.linkIcon)
|
||||
}
|
||||
if level >= requiredBoostSubjectLevel(subject: .emojiStatus, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.emojiStatus)
|
||||
}
|
||||
if level >= requiredBoostSubjectLevel(subject: .wallpaper, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.wallpaper(8))
|
||||
}
|
||||
if level >= requiredBoostSubjectLevel(subject: .customWallpaper, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.customWallpaper)
|
||||
}
|
||||
|
||||
levelItems.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: level, component: AnyComponent(
|
||||
LevelSectionComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
level: level,
|
||||
isFirst: !isFeatures && levelItems.isEmpty,
|
||||
perks: perks.reversed(),
|
||||
isGroup: isGroup
|
||||
)
|
||||
}
|
||||
if !isGroup && linkColorsCount > 0 {
|
||||
perks.append(.linkColor(linkColorsCount))
|
||||
}
|
||||
|
||||
if !isGroup && level >= requiredBoostSubjectLevel(subject: .nameIcon, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.linkIcon)
|
||||
}
|
||||
if level >= requiredBoostSubjectLevel(subject: .emojiStatus, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.emojiStatus)
|
||||
}
|
||||
if level >= requiredBoostSubjectLevel(subject: .wallpaper, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.wallpaper(8))
|
||||
}
|
||||
if level >= requiredBoostSubjectLevel(subject: .customWallpaper, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.customWallpaper)
|
||||
}
|
||||
if !isGroup && level >= requiredBoostSubjectLevel(subject: .noAds, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||
perks.append(.noAds)
|
||||
}
|
||||
|
||||
levelItems.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: level, component: AnyComponent(
|
||||
LevelSectionComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
level: level,
|
||||
isFirst: !isFeatures && levelItems.isEmpty,
|
||||
perks: perks.reversed(),
|
||||
isGroup: isGroup
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if let nextLevels {
|
||||
for level in nextLevels {
|
||||
layoutLevel(level)
|
||||
}
|
||||
}
|
||||
|
||||
if !isGroup {
|
||||
let noAdsLevel = requiredBoostSubjectLevel(subject: .noAds, group: false, context: component.context, configuration: premiumConfiguration)
|
||||
if level < noAdsLevel {
|
||||
layoutLevel(noAdsLevel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1443,6 +1455,8 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
|
|||
titleString = strings.GroupBoost_AudioTranscription
|
||||
case .emojiPack:
|
||||
titleString = strings.GroupBoost_EmojiPack
|
||||
case .noAds:
|
||||
titleString = strings.ChannelBoost_NoAds
|
||||
}
|
||||
} else {
|
||||
titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current
|
||||
|
|
|
|||
|
|
@ -1424,6 +1424,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
|||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
case businessIntro
|
||||
}
|
||||
|
||||
public enum Source: Equatable {
|
||||
|
|
|
|||
|
|
@ -456,6 +456,7 @@ public enum PremiumPerk: CaseIterable {
|
|||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
case businessIntro
|
||||
|
||||
public static var allCases: [PremiumPerk] {
|
||||
return [
|
||||
|
|
@ -488,10 +489,11 @@ public enum PremiumPerk: CaseIterable {
|
|||
return [
|
||||
.businessLocation,
|
||||
.businessHours,
|
||||
.businessGreetingMessage,
|
||||
.businessQuickReplies,
|
||||
.businessGreetingMessage,
|
||||
.businessAwayMessage,
|
||||
.businessChatBots,
|
||||
.businessIntro,
|
||||
.businessChatBots
|
||||
// .emojiStatus,
|
||||
// .folderTags,
|
||||
// .stories,
|
||||
|
|
@ -567,6 +569,8 @@ public enum PremiumPerk: CaseIterable {
|
|||
return "away_message"
|
||||
case .businessChatBots:
|
||||
return "business_bots"
|
||||
case .businessIntro:
|
||||
return "business_intro"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -629,6 +633,8 @@ public enum PremiumPerk: CaseIterable {
|
|||
return strings.Business_AwayMessages
|
||||
case .businessChatBots:
|
||||
return strings.Business_Chatbots
|
||||
case .businessIntro:
|
||||
return strings.Business_Intro
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -691,6 +697,8 @@ public enum PremiumPerk: CaseIterable {
|
|||
return strings.Business_AwayMessagesInfo
|
||||
case .businessChatBots:
|
||||
return strings.Business_ChatbotsInfo
|
||||
case .businessIntro:
|
||||
return strings.Business_IntroInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -753,6 +761,8 @@ public enum PremiumPerk: CaseIterable {
|
|||
return "Premium/BusinessPerk/Away"
|
||||
case .businessChatBots:
|
||||
return "Premium/BusinessPerk/Chatbots"
|
||||
case .businessIntro:
|
||||
return "Premium/BusinessPerk/Intro"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -782,12 +792,13 @@ struct PremiumIntroConfiguration {
|
|||
.premiumStickers,
|
||||
.business
|
||||
], businessPerks: [
|
||||
.businessLocation,
|
||||
.businessHours,
|
||||
.businessQuickReplies,
|
||||
.businessGreetingMessage,
|
||||
.businessAwayMessage,
|
||||
.businessQuickReplies,
|
||||
.businessChatBots,
|
||||
.businessHours,
|
||||
.businessLocation
|
||||
.businessIntro,
|
||||
.businessChatBots
|
||||
// .emojiStatus,
|
||||
// .folderTags,
|
||||
// .stories
|
||||
|
|
@ -853,6 +864,9 @@ struct PremiumIntroConfiguration {
|
|||
if businessPerks.count < 4 {
|
||||
businessPerks = PremiumIntroConfiguration.defaultValue.businessPerks
|
||||
}
|
||||
if !businessPerks.contains(.businessIntro) {
|
||||
businessPerks.append(.businessIntro)
|
||||
}
|
||||
|
||||
return PremiumIntroConfiguration(perks: perks, businessPerks: businessPerks)
|
||||
} else {
|
||||
|
|
@ -2134,6 +2148,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
UIColor(rgb: 0xdb374b),
|
||||
UIColor(rgb: 0xbc4395),
|
||||
UIColor(rgb: 0x9b4fed),
|
||||
UIColor(rgb: 0x8958ff),
|
||||
UIColor(rgb: 0x8958ff)
|
||||
]
|
||||
|
||||
|
|
@ -2244,6 +2259,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||
demoSubject = .businessAwayMessage
|
||||
case .businessChatBots:
|
||||
demoSubject = .businessChatBots
|
||||
case .businessIntro:
|
||||
demoSubject = .businessIntro
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import ConfettiEffect
|
|||
import AvatarNode
|
||||
import TextFormat
|
||||
import RoundedRectWithTailPath
|
||||
import PremiumPeerShortcutComponent
|
||||
|
||||
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
|
|
@ -1140,7 +1141,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
peerShortcutChild = peerShortcut.update(
|
||||
component: Button(
|
||||
content: AnyComponent(
|
||||
PeerShortcutComponent(
|
||||
PremiumPeerShortcutComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
peer: peer
|
||||
|
|
@ -1894,103 +1895,6 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
}
|
||||
|
||||
final class PeerShortcutComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerShortcutComponent, rhs: PeerShortcutComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let backgroundView = UIView()
|
||||
private let avatarNode: AvatarNode
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private var component: PeerShortcutComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 18.0))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundView.clipsToBounds = true
|
||||
self.backgroundView.layer.cornerRadius = 16.0
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: PeerShortcutComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
self.backgroundView.backgroundColor = component.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3)
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: 30.0, height: 30.0))
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
peer: component.peer,
|
||||
synchronousLoad: true
|
||||
)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.medium(15.0), textColor: component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left))
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 50.0, height: availableSize.height)
|
||||
)
|
||||
|
||||
let size = CGSize(width: 30.0 + textSize.width + 20.0, height: 32.0)
|
||||
if let view = self.text.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
let textFrame = CGRect(origin: CGPoint(x: 38.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
view.frame = textFrame
|
||||
}
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class BoostIconComponent: Component {
|
||||
let hasIcon: Bool
|
||||
let text: String
|
||||
|
|
|
|||
|
|
@ -979,6 +979,26 @@ public class PremiumLimitsListScreen: ViewController {
|
|||
)
|
||||
)
|
||||
|
||||
availableItems[.businessIntro] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessIntro,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: videos["business_intro"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_Intro,
|
||||
text: strings.Business_IntroInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if let order = controller.order {
|
||||
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
|
||||
let initialIndex: Int
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ public final class QrCodeScanScreen: ViewController {
|
|||
public enum Subject {
|
||||
case authTransfer(activeSessionsContext: ActiveSessionsContext)
|
||||
case peer
|
||||
case cryptoAddress
|
||||
case custom(info: String)
|
||||
}
|
||||
|
||||
|
|
@ -264,6 +265,8 @@ public final class QrCodeScanScreen: ViewController {
|
|||
strongSelf.controllerNode.updateFocusedRect(nil)
|
||||
}))
|
||||
}
|
||||
case .cryptoAddress:
|
||||
break
|
||||
case .peer:
|
||||
if let _ = URL(string: code) {
|
||||
strongSelf.controllerNode.resolveCode(code: code, completion: { [weak self] result in
|
||||
|
|
@ -479,6 +482,9 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
|||
case .peer:
|
||||
title = ""
|
||||
text = ""
|
||||
case .cryptoAddress:
|
||||
title = ""
|
||||
text = ""
|
||||
case let .custom(info):
|
||||
title = presentationData.strings.AuthSessions_AddDevice_ScanTitle
|
||||
text = info
|
||||
|
|
@ -596,6 +602,8 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
|||
filteredCodes = codes.filter { $0.message.hasPrefix("tg://") }
|
||||
case .peer:
|
||||
filteredCodes = codes.filter { $0.message.hasPrefix("https://t.me/") || $0.message.hasPrefix("t.me/") }
|
||||
case .cryptoAddress:
|
||||
filteredCodes = codes.filter { $0.message.hasPrefix("ton://") }
|
||||
case .custom:
|
||||
filteredCodes = codes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ public final class SolidRoundedButtonTheme: Equatable {
|
|||
self.disabledForegroundColor = disabledForegroundColor
|
||||
}
|
||||
|
||||
public func withUpdated(disabledBackgroundColor: UIColor, disabledForegroundColor: UIColor) -> SolidRoundedButtonTheme {
|
||||
return SolidRoundedButtonTheme(backgroundColor: self.backgroundColor, backgroundColors: self.backgroundColors, foregroundColor: self.foregroundColor, disabledBackgroundColor: disabledBackgroundColor, disabledForegroundColor: disabledForegroundColor)
|
||||
}
|
||||
|
||||
public static func ==(lhs: SolidRoundedButtonTheme, rhs: SolidRoundedButtonTheme) -> Bool {
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -10,35 +10,41 @@ swift_library(
|
|||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/ItemListUI:ItemListUI",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/MergeLists:MergeLists",
|
||||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/GraphCore:GraphCore",
|
||||
"//submodules/GraphUI:GraphUI",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/InviteLinksUI:InviteLinksUI",
|
||||
"//submodules/ShareController:ShareController",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/AlertUI",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/MergeLists",
|
||||
"//submodules/PhotoResources",
|
||||
"//submodules/GraphCore",
|
||||
"//submodules/GraphUI",
|
||||
"//submodules/AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode",
|
||||
"//submodules/ItemListPeerItem",
|
||||
"//submodules/ItemListPeerActionItem",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/PremiumUI",
|
||||
"//submodules/InviteLinksUI",
|
||||
"//submodules/ShareController",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/QrCodeUI",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
322
submodules/StatisticsUI/Sources/CpmSliderItem.swift
Normal file
322
submodules/StatisticsUI/Sources/CpmSliderItem.swift
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramUIPreferences
|
||||
import TelegramPresentationData
|
||||
import LegacyComponents
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
final class CpmSliderItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let value: Int32
|
||||
let animatedEmoji: TelegramMediaFile?
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (Int32) -> Void
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, value: Int32, enabled: Bool, animatedEmoji: TelegramMediaFile?, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.value = value
|
||||
self.animatedEmoji = animatedEmoji
|
||||
self.sectionId = sectionId
|
||||
self.updated = updated
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = CpmSliderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? CpmSliderItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let allowedValues: [Int32] = [1, 2, 3, 4, 5]
|
||||
|
||||
class CpmSliderItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let minTextNode: TextNode
|
||||
private let maxTextNode: TextNode
|
||||
private let textNode: TextNode
|
||||
private var sliderView: TGPhotoEditorSliderView?
|
||||
private var animatedEmojiLayer: InlineStickerItemLayer?
|
||||
private var maxAnimatedEmojiLayer: InlineStickerItemLayer?
|
||||
|
||||
private var item: CpmSliderItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
private var reportedValue: Int32?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
self.minTextNode = TextNode()
|
||||
self.minTextNode.isUserInteractionEnabled = false
|
||||
self.minTextNode.displaysAsynchronously = false
|
||||
|
||||
self.maxTextNode = TextNode()
|
||||
self.maxTextNode.isUserInteractionEnabled = false
|
||||
self.maxTextNode.displaysAsynchronously = false
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.minTextNode)
|
||||
self.addSubnode(self.maxTextNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
let sliderView = TGPhotoEditorSliderView()
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.trackCornerRadius = 2.0
|
||||
sliderView.lineSize = 4.0
|
||||
sliderView.dotSize = 5.0
|
||||
sliderView.minimumValue = 0.0
|
||||
sliderView.maximumValue = 1.0
|
||||
sliderView.startValue = 0.0
|
||||
sliderView.displayEdges = true
|
||||
sliderView.disablesInteractiveTransitionGestureRecognizer = true
|
||||
if let item = self.item, let params = self.layoutParams {
|
||||
sliderView.value = CGFloat(item.value) / 50.0
|
||||
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor
|
||||
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor
|
||||
sliderView.trackColor = item.theme.list.itemAccentColor
|
||||
sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme)
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
|
||||
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
|
||||
}
|
||||
self.view.addSubview(sliderView)
|
||||
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
|
||||
self.sliderView = sliderView
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: CpmSliderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeMinTextLayout = TextNode.asyncLayout(self.minTextNode)
|
||||
let makeMaxTextLayout = TextNode.asyncLayout(self.maxTextNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
var themeUpdated = false
|
||||
if currentItem?.theme !== item.theme {
|
||||
themeUpdated = true
|
||||
}
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
//TODO:localize
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.value == 0 ? "No Ads" : "\(item.value) CPM", font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (minTextLayout, minTextApply) = makeMinTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No Ads", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (maxTextLayout, maxTextApply) = makeMaxTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "50 CPM", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 88.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
bottomStripeOffset = 0.0
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
let _ = textApply()
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: 12.0), size: textLayout.size)
|
||||
strongSelf.textNode.frame = textFrame
|
||||
|
||||
if let animatedEmoji = item.animatedEmoji {
|
||||
let itemSize = floorToScreenPixels(17.0 * 20.0 / 17.0)
|
||||
|
||||
var itemFrame = CGRect(origin: CGPoint(x: textFrame.minX - itemSize / 2.0 - 1.0, y: textFrame.midY), size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
|
||||
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
|
||||
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
|
||||
|
||||
let itemLayer: InlineStickerItemLayer
|
||||
if let current = strongSelf.animatedEmojiLayer {
|
||||
itemLayer = current
|
||||
} else {
|
||||
let pointSize = floor(itemSize * 1.3)
|
||||
itemLayer = InlineStickerItemLayer(context: item.context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: animatedEmoji.fileId.id, file: animatedEmoji, custom: nil), file: animatedEmoji, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.theme.list.mediaPlaceholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
|
||||
strongSelf.animatedEmojiLayer = itemLayer
|
||||
strongSelf.layer.addSublayer(itemLayer)
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
itemLayer.frame = itemFrame
|
||||
}
|
||||
|
||||
let _ = minTextApply()
|
||||
strongSelf.minTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 16.0, y: 16.0), size: minTextLayout.size)
|
||||
|
||||
let _ = maxTextApply()
|
||||
let maxTextFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 16.0 - maxTextLayout.size.width, y: 16.0), size: maxTextLayout.size)
|
||||
strongSelf.maxTextNode.frame = maxTextFrame
|
||||
|
||||
if let animatedEmoji = item.animatedEmoji {
|
||||
let itemSize = floorToScreenPixels(13.0 * 20.0 / 17.0)
|
||||
|
||||
var itemFrame = CGRect(origin: CGPoint(x: maxTextFrame.minX - itemSize / 2.0 - 1.0, y: maxTextFrame.midY), size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
|
||||
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
|
||||
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
|
||||
|
||||
let itemLayer: InlineStickerItemLayer
|
||||
if let current = strongSelf.maxAnimatedEmojiLayer {
|
||||
itemLayer = current
|
||||
} else {
|
||||
let pointSize = floor(itemSize * 1.3)
|
||||
itemLayer = InlineStickerItemLayer(context: item.context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: animatedEmoji.fileId.id, file: animatedEmoji, custom: nil), file: animatedEmoji, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.theme.list.mediaPlaceholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
|
||||
strongSelf.maxAnimatedEmojiLayer = itemLayer
|
||||
strongSelf.layer.addSublayer(itemLayer)
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
|
||||
if let filter = makeMonochromeFilter() {
|
||||
filter.setValue([1.0, 1.0, 1.0, 1.0] as [NSNumber], forKey: "inputColor")
|
||||
filter.setValue(1.0 as NSNumber, forKey: "inputAmount")
|
||||
itemLayer.filters = [filter]
|
||||
}
|
||||
}
|
||||
itemLayer.frame = itemFrame
|
||||
}
|
||||
|
||||
if let sliderView = strongSelf.sliderView {
|
||||
if themeUpdated {
|
||||
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor
|
||||
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor
|
||||
sliderView.trackColor = item.theme.list.itemAccentColor
|
||||
sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme)
|
||||
}
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
|
||||
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
@objc func sliderValueChanged() {
|
||||
guard let item = self.item, let sliderView = self.sliderView else {
|
||||
return
|
||||
}
|
||||
item.updated(Int32(sliderView.value * 50.0))
|
||||
}
|
||||
}
|
||||
|
|
@ -381,7 +381,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||
let .topInvitersTitle(_, text, dates):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
||||
case let .overview(_, stats):
|
||||
return StatsOverviewItem(presentationData: presentationData, isGroup: true, stats: stats, sectionId: self.section, style: .blocks)
|
||||
return StatsOverviewItem(context: arguments.context, presentationData: presentationData, isGroup: true, stats: stats, sectionId: self.section, style: .blocks)
|
||||
case let .growthGraph(_, _, _, graph, type),
|
||||
let .membersGraph(_, _, _, graph, type),
|
||||
let .newMembersBySourceGraph(_, _, _, graph, type),
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||
let .publicForwardsTitle(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .overview(_, stats, storyViews, publicShares):
|
||||
return StatsOverviewItem(presentationData: presentationData, isGroup: false, stats: stats as! Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks)
|
||||
return StatsOverviewItem(context: arguments.context, presentationData: presentationData, isGroup: false, stats: stats as! Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks)
|
||||
case let .interactionsGraph(_, _, _, graph, type, noInitialZoom), let .reactionsGraph(_, _, _, graph, type, noInitialZoom):
|
||||
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, noInitialZoom: noInitialZoom, getDetailsData: { date, completion in
|
||||
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
|
||||
|
|
|
|||
488
submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift
Normal file
488
submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift
Normal file
|
|
@ -0,0 +1,488 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import SolidRoundedButtonNode
|
||||
import TelegramCore
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
|
||||
final class MonetizationBalanceItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let presentationData: ItemListPresentationData
|
||||
let stats: MonetizationStats
|
||||
let animatedEmoji: TelegramMediaFile?
|
||||
let address: String
|
||||
let withdrawAction: () -> Void
|
||||
let qrAction: () -> Void
|
||||
let action: (() -> Void)?
|
||||
let textUpdated: (String) -> Void
|
||||
let shouldUpdateText: (String) -> Bool
|
||||
let processPaste: ((String) -> Void)?
|
||||
let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
presentationData: ItemListPresentationData,
|
||||
stats: MonetizationStats,
|
||||
animatedEmoji: TelegramMediaFile?,
|
||||
address: String,
|
||||
withdrawAction: @escaping () -> Void,
|
||||
qrAction: @escaping () -> Void,
|
||||
action: (() -> Void)?,
|
||||
textUpdated: @escaping (String) -> Void,
|
||||
shouldUpdateText: @escaping (String) -> Bool,
|
||||
processPaste: ((String) -> Void)?,
|
||||
sectionId: ItemListSectionId,
|
||||
style: ItemListStyle
|
||||
) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.stats = stats
|
||||
self.animatedEmoji = animatedEmoji
|
||||
self.address = address
|
||||
self.withdrawAction = withdrawAction
|
||||
self.qrAction = qrAction
|
||||
self.action = action
|
||||
self.textUpdated = textUpdated
|
||||
self.shouldUpdateText = shouldUpdateText
|
||||
self.processPaste = processPaste
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = MonetizationBalanceItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? MonetizationBalanceItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectable: Bool = false
|
||||
}
|
||||
|
||||
final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode, ASEditableTextNodeDelegate {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private var animatedEmojiLayer: InlineStickerItemLayer?
|
||||
private let balanceTextNode: TextNode
|
||||
private let valueTextNode: TextNode
|
||||
|
||||
private let fieldNode: ASImageNode
|
||||
private let textClippingNode: ASDisplayNode
|
||||
private let textNode: EditableTextNode
|
||||
private let measureTextNode: TextNode
|
||||
|
||||
private let qrButtonNode: HighlightableButtonNode
|
||||
private var withdrawButtonNode: SolidRoundedButtonNode?
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: MonetizationBalanceItem?
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.balanceTextNode = TextNode()
|
||||
self.balanceTextNode.isUserInteractionEnabled = false
|
||||
self.balanceTextNode.displaysAsynchronously = false
|
||||
|
||||
self.valueTextNode = TextNode()
|
||||
self.valueTextNode.isUserInteractionEnabled = false
|
||||
self.valueTextNode.displaysAsynchronously = false
|
||||
|
||||
self.fieldNode = ASImageNode()
|
||||
self.fieldNode.displaysAsynchronously = false
|
||||
self.fieldNode.displayWithoutProcessing = true
|
||||
|
||||
self.textClippingNode = ASDisplayNode()
|
||||
self.textClippingNode.clipsToBounds = true
|
||||
|
||||
self.textNode = EditableTextNode()
|
||||
self.measureTextNode = TextNode()
|
||||
|
||||
self.qrButtonNode = HighlightableButtonNode()
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.balanceTextNode)
|
||||
self.addSubnode(self.valueTextNode)
|
||||
self.addSubnode(self.fieldNode)
|
||||
self.addSubnode(self.qrButtonNode)
|
||||
|
||||
self.textClippingNode.addSubnode(self.textNode)
|
||||
self.addSubnode(self.textClippingNode)
|
||||
|
||||
self.qrButtonNode.addTarget(self, action: #selector(self.qrButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
var textColor: UIColor = .black
|
||||
if let item = self.item {
|
||||
textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
||||
} else {
|
||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
||||
}
|
||||
self.textNode.clipsToBounds = true
|
||||
self.textNode.delegate = self
|
||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
}
|
||||
|
||||
@objc private func qrButtonPressed() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
item.qrAction()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: MonetizationBalanceItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
let makeBalanceTextLayout = TextNode.asyncLayout(self.balanceTextNode)
|
||||
let makeValueTextLayout = TextNode.asyncLayout(self.valueTextNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: PresentationTheme?
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
let itemBackgroundColor: UIColor
|
||||
let itemSeparatorColor: UIColor
|
||||
|
||||
let leftInset = 16.0 + params.leftInset
|
||||
let rightInset = 16.0 + params.rightInset
|
||||
let constrainedWidth = params.width - leftInset - rightInset
|
||||
|
||||
let integralFont = Font.with(size: 48.0, design: .round, weight: .semibold)
|
||||
let fractionalFont = Font.with(size: 24.0, design: .round, weight: .semibold)
|
||||
|
||||
let cryptoValue = formatBalanceText(item.stats.availableBalance.cryptoAmount, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
|
||||
|
||||
let amountString = amountAttributedString(cryptoValue, integralFont: integralFont, fractionalFont: fractionalFont, color: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
|
||||
let (balanceLayout, balanceApply) = makeBalanceTextLayout(TextNodeLayoutArguments(attributedString: amountString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let value = "≈$100"
|
||||
let (valueLayout, valueApply) = makeValueTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: value, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var measureText = item.address
|
||||
if measureText.hasSuffix("\n") || measureText.isEmpty {
|
||||
measureText += "|"
|
||||
}
|
||||
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: .black)
|
||||
let attributedText = NSAttributedString(string: item.address, font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let (textLayout, _) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0 - 36.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let attributedPlaceholderText = NSAttributedString(string: "Enter your TON address", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemPlaceholderTextColor)
|
||||
|
||||
let verticalInset: CGFloat = 16.0
|
||||
let fieldHeight: CGFloat = max(52.0, textLayout.size.height + 32.0)
|
||||
let fieldSpacing: CGFloat = 16.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
|
||||
var height: CGFloat = verticalInset * 2.0 + balanceLayout.size.height + 7.0
|
||||
if valueLayout.size.height > 0.0 {
|
||||
height += valueLayout.size.height
|
||||
height += fieldHeight + fieldSpacing + buttonHeight
|
||||
}
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
itemSeparatorColor = .clear
|
||||
insets = UIEdgeInsets()
|
||||
case .blocks:
|
||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
}
|
||||
|
||||
contentSize = CGSize(width: params.width, height: height)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
let _ = balanceApply()
|
||||
let _ = valueApply()
|
||||
|
||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||
strongSelf.activateArea.accessibilityTraits = []
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor)
|
||||
|
||||
strongSelf.qrButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/QrButtonIcon"), color: item.presentationData.theme.list.itemAccentColor), for: .normal)
|
||||
}
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
if strongSelf.backgroundNode.supernode != nil {
|
||||
strongSelf.backgroundNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode != nil {
|
||||
strongSelf.topStripeNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
|
||||
}
|
||||
if strongSelf.maskNode.supernode != nil {
|
||||
strongSelf.maskNode.removeFromSupernode()
|
||||
}
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
||||
case .blocks:
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||
}
|
||||
|
||||
var emojiItemFrame: CGRect = .zero
|
||||
var emojiItemSize: CGFloat = 0.0
|
||||
if let animatedEmoji = item.animatedEmoji {
|
||||
emojiItemSize = floorToScreenPixels(46.0 * 20.0 / 17.0)
|
||||
|
||||
emojiItemFrame = CGRect(origin: CGPoint(x: -emojiItemSize / 2.0 - 5.0, y: -3.0), size: CGSize()).insetBy(dx: -emojiItemSize / 2.0, dy: -emojiItemSize / 2.0)
|
||||
emojiItemFrame.origin.x = floorToScreenPixels(emojiItemFrame.origin.x)
|
||||
emojiItemFrame.origin.y = floorToScreenPixels(emojiItemFrame.origin.y)
|
||||
|
||||
let itemLayer: InlineStickerItemLayer
|
||||
if let current = strongSelf.animatedEmojiLayer {
|
||||
itemLayer = current
|
||||
} else {
|
||||
let pointSize = floor(emojiItemSize * 1.3)
|
||||
itemLayer = InlineStickerItemLayer(context: item.context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: animatedEmoji.fileId.id, file: animatedEmoji, custom: nil), file: animatedEmoji, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
|
||||
strongSelf.animatedEmojiLayer = itemLayer
|
||||
strongSelf.layer.addSublayer(itemLayer)
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
}
|
||||
|
||||
let balanceTotalWidth: CGFloat = emojiItemSize + balanceLayout.size.width
|
||||
let balanceTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - balanceTotalWidth) / 2.0) + emojiItemSize, y: 13.0), size: balanceLayout.size)
|
||||
strongSelf.balanceTextNode.frame = balanceTextFrame
|
||||
strongSelf.animatedEmojiLayer?.frame = emojiItemFrame.offsetBy(dx: balanceTextFrame.minX, dy: balanceTextFrame.midY)
|
||||
|
||||
strongSelf.valueTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - valueLayout.size.width) / 2.0), y: balanceTextFrame.maxY - 5.0), size: valueLayout.size)
|
||||
|
||||
strongSelf.textNode.textView.autocapitalizationType = .none
|
||||
strongSelf.textNode.textView.autocorrectionType = .no
|
||||
strongSelf.textNode.textView.returnKeyType = .done
|
||||
|
||||
if let currentText = strongSelf.textNode.attributedText {
|
||||
if currentText.string != attributedText.string || updatedTheme != nil {
|
||||
strongSelf.textNode.attributedText = attributedText
|
||||
}
|
||||
} else {
|
||||
strongSelf.textNode.attributedText = attributedText
|
||||
}
|
||||
|
||||
if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {
|
||||
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
||||
}
|
||||
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
||||
|
||||
let textTopInset: CGFloat = 108.0
|
||||
if strongSelf.animationForKey("apparentHeight") == nil {
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset + 12.0, y: textTopInset + 15.0), size: CGSize(width: params.width - leftInset - rightInset - 12.0 - 36.0, height: textLayout.size.height))
|
||||
}
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset - 12.0 - 36.0, height: textLayout.size.height + 1.0))
|
||||
|
||||
let fieldFrame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - rightInset, height: fieldHeight))
|
||||
strongSelf.fieldNode.frame = fieldFrame
|
||||
|
||||
let qrButtonSize = CGSize(width: 32.0, height: 32.0)
|
||||
let qrButtonFrame = CGRect(origin: CGPoint(x: fieldFrame.maxX - qrButtonSize.width - 5.0, y: fieldFrame.midY - qrButtonSize.height / 2.0), size: qrButtonSize)
|
||||
strongSelf.qrButtonNode.frame = qrButtonFrame
|
||||
|
||||
let withdrawButtonNode: SolidRoundedButtonNode
|
||||
if let currentShareButtonNode = strongSelf.withdrawButtonNode {
|
||||
withdrawButtonNode = currentShareButtonNode
|
||||
} else {
|
||||
var buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
|
||||
buttonTheme = buttonTheme.withUpdated(disabledBackgroundColor: buttonTheme.backgroundColor, disabledForegroundColor: buttonTheme.foregroundColor.withAlphaComponent(0.6))
|
||||
withdrawButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: buttonHeight, cornerRadius: 11.0)
|
||||
withdrawButtonNode.pressed = { [weak self] in
|
||||
if let self, let item = self.item {
|
||||
item.withdrawAction()
|
||||
}
|
||||
}
|
||||
strongSelf.addSubnode(withdrawButtonNode)
|
||||
strongSelf.withdrawButtonNode = withdrawButtonNode
|
||||
}
|
||||
if cryptoValue != "0" {
|
||||
withdrawButtonNode.title = "Transfer \(cryptoValue) TON"
|
||||
}
|
||||
withdrawButtonNode.isEnabled = (strongSelf.textNode.attributedText?.string.count ?? 0) == walletAddressLength
|
||||
|
||||
let buttonWidth = contentSize.width - leftInset - rightInset
|
||||
let _ = withdrawButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
||||
withdrawButtonNode.frame = CGRect(x: leftInset, y: fieldFrame.maxY + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
if let item = self.item {
|
||||
if text.count > 1, let processPaste = item.processPaste {
|
||||
processPaste(text)
|
||||
return false
|
||||
}
|
||||
|
||||
if let action = item.action, text == "\n" {
|
||||
action()
|
||||
return false
|
||||
}
|
||||
|
||||
let newText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
if !item.shouldUpdateText(newText) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
if let item = self.item {
|
||||
if let text = self.textNode.attributedText {
|
||||
let updatedText = text.string
|
||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
if text.string != updatedAttributedText.string {
|
||||
self.textNode.attributedText = updatedAttributedText
|
||||
}
|
||||
self.withdrawButtonNode?.isEnabled = (self.textNode.attributedText?.string.count ?? 0) == walletAddressLength
|
||||
item.textUpdated(updatedText)
|
||||
} else {
|
||||
item.textUpdated("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
if let _ = self.item {
|
||||
let text: String? = UIPasteboard.general.string
|
||||
if let _ = text {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
591
submodules/StatisticsUI/Sources/MonetizationIntroScreen.swift
Normal file
591
submodules/StatisticsUI/Sources/MonetizationIntroScreen.swift
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Markdown
|
||||
import TextFormat
|
||||
import TelegramPresentationData
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import BundleIconComponent
|
||||
import BalancedTextComponent
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import SolidRoundedButtonComponent
|
||||
import LottieComponent
|
||||
import AccountContext
|
||||
|
||||
private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let animatedEmojis: [String: TelegramMediaFile]
|
||||
let openMore: () -> Void
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
animatedEmojis: [String: TelegramMediaFile],
|
||||
openMore: @escaping () -> Void,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.animatedEmojis = animatedEmojis
|
||||
self.openMore = openMore
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var cachedIconImage: (UIImage, PresentationTheme)?
|
||||
var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
let playOnce = ActionSlot<Void>()
|
||||
private var didPlayAnimation = false
|
||||
|
||||
func playAnimationIfNeeded() {
|
||||
guard !self.didPlayAnimation else {
|
||||
return
|
||||
}
|
||||
self.didPlayAnimation = true
|
||||
self.playOnce.invoke(Void())
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State()
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let iconBackground = Child(Image.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
|
||||
let title = Child(BalancedTextComponent.self)
|
||||
let list = Child(List<Empty>.self)
|
||||
let actionButton = Child(SolidRoundedButtonComponent.self)
|
||||
|
||||
let infoBackground = Child(RoundedRectangle.self)
|
||||
let infoTitle = Child(MultilineTextWithEntitiesComponent.self)
|
||||
let infoText = Child(MultilineTextComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let component = context.component
|
||||
let state = context.state
|
||||
|
||||
let theme = environment.theme
|
||||
// let strings = environment.strings
|
||||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
|
||||
|
||||
let titleFont = Font.semibold(20.0)
|
||||
let textFont = Font.regular(15.0)
|
||||
|
||||
let textColor = theme.actionSheet.primaryTextColor
|
||||
let secondaryTextColor = theme.actionSheet.secondaryTextColor
|
||||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
//TODO:localize
|
||||
|
||||
let spacing: CGFloat = 16.0
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 32.0)
|
||||
|
||||
let iconSize = CGSize(width: 90.0, height: 90.0)
|
||||
let gradientImage: UIImage
|
||||
|
||||
if let (current, currentTheme) = state.cachedIconImage, currentTheme === theme {
|
||||
gradientImage = current
|
||||
} else {
|
||||
gradientImage = generateGradientFilledCircleImage(diameter: iconSize.width, colors: [
|
||||
UIColor(rgb: 0x4bbb45).cgColor,
|
||||
UIColor(rgb: 0x9ad164).cgColor
|
||||
])!
|
||||
context.state.cachedIconImage = (gradientImage, theme)
|
||||
}
|
||||
|
||||
let iconBackground = iconBackground.update(
|
||||
component: Image(image: gradientImage),
|
||||
availableSize: iconSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(iconBackground
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(name: "Chart/Monetization", tintColor: theme.list.itemCheckColors.foregroundColor),
|
||||
availableSize: CGSize(width: 90, height: 90),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += iconSize.height
|
||||
contentSize.height += spacing + 5.0
|
||||
|
||||
let title = title.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(string: "Earn From Your Channel", font: titleFont, textColor: textColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += spacing
|
||||
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "ads",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "Telegram Ads",
|
||||
titleColor: textColor,
|
||||
text: "Telegram can display ads in your channel.",
|
||||
textColor: secondaryTextColor,
|
||||
iconName: "Chart/Ads",
|
||||
iconColor: linkColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "split",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "50:50 Revenue Split",
|
||||
titleColor: textColor,
|
||||
text: "You receive 50% of the ad revenue in TON.",
|
||||
textColor: secondaryTextColor,
|
||||
iconName: "Chart/Split",
|
||||
iconColor: linkColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "withdrawal",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "Flexible Withdrawals",
|
||||
titleColor: textColor,
|
||||
text: "You can withdraw your TON any time.",
|
||||
textColor: secondaryTextColor,
|
||||
iconName: "Chart/Withdrawal",
|
||||
iconColor: linkColor
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
let list = list.update(
|
||||
component: List(items),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset, height: 10000.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(list
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += list.size.height
|
||||
contentSize.height += spacing - 9.0
|
||||
|
||||
let infoTitleString = "What's #TON?"//.replacingOccurrences(of: "#", with: "# ")
|
||||
let infoTitleAttributedString = NSMutableAttributedString(string: infoTitleString, font: titleFont, textColor: textColor)
|
||||
let range = (infoTitleAttributedString.string as NSString).range(of: "#")
|
||||
if range.location != NSNotFound, let emojiFile = component.animatedEmojis["💎"] {
|
||||
infoTitleAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
|
||||
}
|
||||
let infoTitle = infoTitle.update(
|
||||
component: MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: environment.theme.list.mediaPlaceholderColor,
|
||||
text: .plain(infoTitleAttributedString),
|
||||
horizontalAlignment: .center
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
||||
}
|
||||
|
||||
let infoString = "TON is a blockchain platform and cryptocurrency that Telegram uses for its record scalability and ultra low commissions on transactions.\n[Learn More >]()"
|
||||
let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
|
||||
infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string))
|
||||
}
|
||||
let infoText = infoText.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(infoAttributedString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - (textSideInset + sideInset - 2.0) * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let infoPadding: CGFloat = 17.0
|
||||
let infoSpacing: CGFloat = 12.0
|
||||
let totalInfoHeight = infoPadding + infoTitle.size.height + infoSpacing + infoText.size.height + infoPadding
|
||||
|
||||
let infoBackground = infoBackground.update(
|
||||
component: RoundedRectangle(
|
||||
color: theme.list.blocksBackgroundColor,
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: totalInfoHeight),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(infoBackground
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoBackground.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += infoPadding
|
||||
|
||||
context.add(infoTitle
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoTitle.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += infoTitle.size.height
|
||||
contentSize.height += infoSpacing
|
||||
|
||||
context.add(infoText
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoText.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += infoText.size.height
|
||||
contentSize.height += infoPadding
|
||||
contentSize.height += spacing
|
||||
|
||||
let actionButton = actionButton.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: "Understood",
|
||||
theme: SolidRoundedButtonComponent.Theme(
|
||||
backgroundColor: theme.list.itemCheckColors.fillColor,
|
||||
backgroundColors: [],
|
||||
foregroundColor: theme.list.itemCheckColors.foregroundColor
|
||||
),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: false,
|
||||
iconName: nil,
|
||||
animationName: nil,
|
||||
iconPosition: .left,
|
||||
action: {
|
||||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(actionButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += actionButton.size.height
|
||||
contentSize.height += 22.0
|
||||
|
||||
contentSize.height += environment.safeInsets.bottom
|
||||
|
||||
state.playAnimationIfNeeded()
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SheetContainerComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let animatedEmojis: [String: TelegramMediaFile]
|
||||
let openMore: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
animatedEmojis: [String: TelegramMediaFile],
|
||||
openMore: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.animatedEmojis = animatedEmojis
|
||||
self.openMore = openMore
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContainerComponent, rhs: SheetContainerComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(SheetComponent<EnvironmentType>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
animatedEmojis: context.component.animatedEmojis,
|
||||
openMore: context.component.openMore,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
externalState: sheetExternalState,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
|
||||
let layout = ContainerViewLayout(
|
||||
size: context.availableSize,
|
||||
metrics: environment.metrics,
|
||||
deviceMetrics: environment.deviceMetrics,
|
||||
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0),
|
||||
safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
|
||||
additionalInsets: .zero,
|
||||
statusBarHeight: environment.statusBarHeight,
|
||||
inputHeight: nil,
|
||||
inputHeightIsInteractivellyChanging: false,
|
||||
inVoiceOver: false
|
||||
)
|
||||
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
|
||||
}
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class MonetizationIntroScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let animatedEmojis: [String: TelegramMediaFile]
|
||||
private var openMore: (() -> Void)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
animatedEmojis: [String: TelegramMediaFile],
|
||||
openMore: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.animatedEmojis = animatedEmojis
|
||||
self.openMore = openMore
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: SheetContainerComponent(
|
||||
context: context,
|
||||
animatedEmojis: animatedEmojis,
|
||||
openMore: openMore
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
}
|
||||
|
||||
func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ParagraphComponent: CombinedComponent {
|
||||
let title: String
|
||||
let titleColor: UIColor
|
||||
let text: String
|
||||
let textColor: UIColor
|
||||
let iconName: String
|
||||
let iconColor: UIColor
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
titleColor: UIColor,
|
||||
text: String,
|
||||
textColor: UIColor,
|
||||
iconName: String,
|
||||
iconColor: UIColor
|
||||
) {
|
||||
self.title = title
|
||||
self.titleColor = titleColor
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.iconName = iconName
|
||||
self.iconColor = iconColor
|
||||
}
|
||||
|
||||
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.titleColor != rhs.titleColor {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
if lhs.iconColor != rhs.iconColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
|
||||
let leftInset: CGFloat = 64.0
|
||||
let rightInset: CGFloat = 32.0
|
||||
let textSideInset: CGFloat = leftInset + 8.0
|
||||
let spacing: CGFloat = 5.0
|
||||
|
||||
let textTopInset: CGFloat = 9.0
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.title,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.titleColor,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = component.textColor
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: component.text, attributes: markdownAttributes),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: component.iconName,
|
||||
tintColor: component.iconColor
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
|
||||
context.add(text
|
||||
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
|
||||
)
|
||||
|
||||
context.add(icon
|
||||
.position(CGPoint(x: 47.0, y: textTopInset + 18.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
62
submodules/StatisticsUI/Sources/MonetizationUtils.swift
Normal file
62
submodules/StatisticsUI/Sources/MonetizationUtils.swift
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
|
||||
let walletAddressLength: Int = 48
|
||||
|
||||
func formatAddress(_ address: String) -> String {
|
||||
var address = address
|
||||
address.insert("\n", at: address.index(address.startIndex, offsetBy: address.count / 2))
|
||||
return address
|
||||
}
|
||||
|
||||
func formatBalanceText(_ value: Int64, decimalSeparator: String, showPlus: Bool = false) -> String {
|
||||
var balanceText = "\(abs(value))"
|
||||
while balanceText.count < 10 {
|
||||
balanceText.insert("0", at: balanceText.startIndex)
|
||||
}
|
||||
balanceText.insert(contentsOf: decimalSeparator, at: balanceText.index(balanceText.endIndex, offsetBy: -9))
|
||||
while true {
|
||||
if balanceText.hasSuffix("0") {
|
||||
if balanceText.hasSuffix("\(decimalSeparator)0") {
|
||||
balanceText.removeLast()
|
||||
balanceText.removeLast()
|
||||
break
|
||||
} else {
|
||||
balanceText.removeLast()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if value < 0 {
|
||||
balanceText.insert("-", at: balanceText.startIndex)
|
||||
} else if showPlus {
|
||||
balanceText.insert("+", at: balanceText.startIndex)
|
||||
}
|
||||
return balanceText
|
||||
}
|
||||
|
||||
private let invalidAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted
|
||||
func isValidAddress(_ address: String, exactLength: Bool = false) -> Bool {
|
||||
if address.count > walletAddressLength || address.rangeOfCharacter(from: invalidAddressCharacters) != nil {
|
||||
return false
|
||||
}
|
||||
if exactLength && address.count != walletAddressLength {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private let amountDelimeterCharacters = CharacterSet(charactersIn: "0123456789-+").inverted
|
||||
func amountAttributedString(_ string: String, integralFont: UIFont, fractionalFont: UIFont, color: UIColor) -> NSAttributedString {
|
||||
let result = NSMutableAttributedString()
|
||||
if let range = string.rangeOfCharacter(from: amountDelimeterCharacters) {
|
||||
let integralPart = String(string[..<range.lowerBound])
|
||||
let fractionalPart = String(string[range.lowerBound...])
|
||||
result.append(NSAttributedString(string: integralPart, font: integralFont, textColor: color))
|
||||
result.append(NSAttributedString(string: fractionalPart, font: fractionalFont, textColor: color))
|
||||
} else {
|
||||
result.append(NSAttributedString(string: string, font: integralFont, textColor: color))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -7,6 +7,9 @@ import TelegramCore
|
|||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
|
||||
protocol Stats {
|
||||
|
||||
|
|
@ -32,21 +35,29 @@ extension StoryStats: Stats {
|
|||
|
||||
}
|
||||
|
||||
extension MonetizationStats: Stats {
|
||||
|
||||
}
|
||||
|
||||
class StatsOverviewItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let presentationData: ItemListPresentationData
|
||||
let isGroup: Bool
|
||||
let stats: Stats
|
||||
let storyViews: EngineStoryItem.Views?
|
||||
let publicShares: Int32?
|
||||
let animatedEmoji: TelegramMediaFile?
|
||||
let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
|
||||
init(presentationData: ItemListPresentationData, isGroup: Bool, stats: Stats, storyViews: EngineStoryItem.Views? = nil, publicShares: Int32? = nil, sectionId: ItemListSectionId, style: ItemListStyle) {
|
||||
init(context: AccountContext, presentationData: ItemListPresentationData, isGroup: Bool, stats: Stats, storyViews: EngineStoryItem.Views? = nil, publicShares: Int32? = nil, animatedEmoji: TelegramMediaFile? = nil, sectionId: ItemListSectionId, style: ItemListStyle) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.isGroup = isGroup
|
||||
self.stats = stats
|
||||
self.storyViews = storyViews
|
||||
self.publicShares = publicShares
|
||||
self.animatedEmoji = animatedEmoji
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
}
|
||||
|
|
@ -97,6 +108,7 @@ private final class ValueItemNode: ASDisplayNode {
|
|||
private let valueNode: TextNode
|
||||
private let titleNode: TextNode
|
||||
private let deltaNode: TextNode
|
||||
private var animatedEmojiLayer: InlineStickerItemLayer?
|
||||
|
||||
var currentBackgroundColor: UIColor?
|
||||
var pressed: (() -> Void)?
|
||||
|
|
@ -115,13 +127,13 @@ private final class ValueItemNode: ASDisplayNode {
|
|||
self.addSubnode(self.deltaNode)
|
||||
}
|
||||
|
||||
static func asyncLayout(_ current: ValueItemNode?) -> (_ width: CGFloat, _ presentationData: ItemListPresentationData, _ value: String, _ title: String, _ delta: (String, DeltaColor)?) -> (CGSize, () -> ValueItemNode) {
|
||||
static func asyncLayout(_ current: ValueItemNode?) -> (_ context: AccountContext, _ width: CGFloat, _ presentationData: ItemListPresentationData, _ value: String, _ title: String, _ delta: (String, DeltaColor)?, _ animatedEmoji: TelegramMediaFile?) -> (CGSize, () -> ValueItemNode) {
|
||||
|
||||
let maybeMakeValueLayout = (current?.valueNode).flatMap(TextNode.asyncLayout)
|
||||
let maybeMakeTitleLayout = (current?.titleNode).flatMap(TextNode.asyncLayout)
|
||||
let maybeMakeDeltaLayout = (current?.deltaNode).flatMap(TextNode.asyncLayout)
|
||||
|
||||
return { width, presentationData, value, title, delta in
|
||||
return { context, width, presentationData, value, title, delta, animatedEmoji in
|
||||
let targetNode: ValueItemNode
|
||||
if let current = current {
|
||||
targetNode = current
|
||||
|
|
@ -150,7 +162,8 @@ private final class ValueItemNode: ASDisplayNode {
|
|||
makeDeltaLayout = TextNode.asyncLayout(targetNode.deltaNode)
|
||||
}
|
||||
|
||||
let valueFont = Font.semibold(presentationData.fontSize.itemListBaseFontSize)
|
||||
let fontSize = presentationData.fontSize.itemListBaseFontSize
|
||||
let valueFont = Font.semibold(fontSize)
|
||||
let titleFont = Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize)
|
||||
let deltaFont = Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize)
|
||||
|
||||
|
|
@ -170,6 +183,7 @@ private final class ValueItemNode: ASDisplayNode {
|
|||
} else {
|
||||
deltaColor = presentationData.theme.list.freeTextErrorColor
|
||||
}
|
||||
let placeholderColor = presentationData.theme.list.mediaPlaceholderColor
|
||||
|
||||
let constrainedSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
|
||||
let (valueLayout, valueApply) = makeValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: value, font: valueFont, textColor: valueColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
|
@ -185,9 +199,33 @@ private final class ValueItemNode: ASDisplayNode {
|
|||
let _ = titleApply()
|
||||
let _ = deltaApply()
|
||||
|
||||
let valueFrame = CGRect(origin: .zero, size: valueLayout.size)
|
||||
var valueOffset: CGFloat = 0.0
|
||||
if let animatedEmoji {
|
||||
let itemSize = floorToScreenPixels(fontSize * 20.0 / 17.0)
|
||||
|
||||
var itemFrame = CGRect(origin: CGPoint(x: itemSize / 2.0 - 1.0, y: itemSize / 2.0), size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
|
||||
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
|
||||
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
|
||||
|
||||
let itemLayer: InlineStickerItemLayer
|
||||
if let current = targetNode.animatedEmojiLayer {
|
||||
itemLayer = current
|
||||
} else {
|
||||
let pointSize = floor(itemSize * 1.3)
|
||||
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: animatedEmoji.fileId.id, file: animatedEmoji, custom: nil), file: animatedEmoji, cache: context.animationCache, renderer: context.animationRenderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
|
||||
targetNode.animatedEmojiLayer = itemLayer
|
||||
targetNode.layer.addSublayer(itemLayer)
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
valueOffset += 22.0
|
||||
|
||||
itemLayer.frame = itemFrame
|
||||
}
|
||||
|
||||
let valueFrame = CGRect(origin: CGPoint(x: valueOffset, y: 0.0), size: valueLayout.size)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: valueFrame.maxY), size: titleLayout.size)
|
||||
let deltaFrame = CGRect(origin: CGPoint(x: valueFrame.maxX + horizontalSpacing, y: valueFrame.maxY - deltaLayout.size.height - 2.0), size: deltaLayout.size)
|
||||
let deltaFrame = CGRect(origin: CGPoint(x: valueFrame.maxX + horizontalSpacing, y: valueFrame.maxY - deltaLayout.size.height - 2.0 - UIScreenPixel), size: deltaLayout.size)
|
||||
|
||||
targetNode.valueNode.frame = valueFrame
|
||||
targetNode.titleNode.frame = titleFrame
|
||||
|
|
@ -296,7 +334,7 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
}
|
||||
|
||||
let twoColumnLayout = "".isEmpty
|
||||
var twoColumnLayout = true
|
||||
|
||||
var topLeftItemLayoutAndApply: (CGSize, () -> ValueItemNode)?
|
||||
var topRightItemLayoutAndApply: (CGSize, () -> ValueItemNode)?
|
||||
|
|
@ -321,78 +359,96 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
|
||||
if let stats = item.stats as? MessageStats {
|
||||
topLeftItemLayoutAndApply = makeTopLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(stats.views),
|
||||
item.presentationData.strings.Stats_Message_Views,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
topRightItemLayoutAndApply = makeTopRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
item.publicShares.flatMap { compactNumericCountString(Int($0)) } ?? "–",
|
||||
item.presentationData.strings.Stats_Message_PublicShares,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
middle1LeftItemLayoutAndApply = makeMiddle1LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(stats.reactions),
|
||||
item.presentationData.strings.Stats_Message_Reactions,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
middle1RightItemLayoutAndApply = makeMiddle1RightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
item.publicShares.flatMap { "≈\( compactNumericCountString(max(0, stats.forwards - Int($0))))" } ?? "–",
|
||||
item.presentationData.strings.Stats_Message_PrivateShares,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
height += topRightItemLayoutAndApply!.0.height * 2.0 + verticalSpacing
|
||||
} else if let _ = item.stats as? StoryStats, let views = item.storyViews {
|
||||
topLeftItemLayoutAndApply = makeTopLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(views.seenCount),
|
||||
item.presentationData.strings.Stats_Message_Views,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
topRightItemLayoutAndApply = makeTopRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
item.publicShares.flatMap { compactNumericCountString(Int($0)) } ?? "–",
|
||||
item.presentationData.strings.Stats_Message_PublicShares,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
middle1LeftItemLayoutAndApply = makeMiddle1LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(views.reactedCount),
|
||||
item.presentationData.strings.Stats_Message_Reactions,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
middle1RightItemLayoutAndApply = makeMiddle1RightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
item.publicShares.flatMap { "≈\( compactNumericCountString(max(0, views.forwardCount - Int($0))))" } ?? "–",
|
||||
item.presentationData.strings.Stats_Message_PrivateShares,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
height += topRightItemLayoutAndApply!.0.height * 2.0 + verticalSpacing
|
||||
} else if let stats = item.stats as? ChannelBoostStatus {
|
||||
topLeftItemLayoutAndApply = makeTopLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"\(stats.level)",
|
||||
item.presentationData.strings.Stats_Boosts_Level,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
|
|
@ -402,18 +458,22 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
}
|
||||
|
||||
topRightItemLayoutAndApply = makeTopRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"≈\(Int(stats.premiumAudience?.value ?? 0))",
|
||||
item.isGroup ? item.presentationData.strings.Stats_Boosts_PremiumMembers : item.presentationData.strings.Stats_Boosts_PremiumSubscribers,
|
||||
(String(format: "%.02f%%", premiumSubscribers * 100.0), .generic)
|
||||
(String(format: "%.02f%%", premiumSubscribers * 100.0), .generic),
|
||||
nil
|
||||
)
|
||||
|
||||
middle1LeftItemLayoutAndApply = makeMiddle1LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"\(stats.boosts)",
|
||||
item.presentationData.strings.Stats_Boosts_ExistingBoosts,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
|
|
@ -424,10 +484,12 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
boostsLeft = 0
|
||||
}
|
||||
middle1RightItemLayoutAndApply = makeMiddle1RightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"\(boostsLeft)",
|
||||
item.presentationData.strings.Stats_Boosts_BoostsToLevelUp,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
|
|
@ -447,11 +509,13 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
|
||||
let followersDelta = deltaText(stats.followers)
|
||||
topLeftItemLayoutAndApply = makeTopLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(Int(stats.followers.current)),
|
||||
item.presentationData.strings.Stats_Followers,
|
||||
(followersDelta.text, followersDelta.positive ? .positive : .negative)
|
||||
(followersDelta.text, followersDelta.positive ? .positive : .negative),
|
||||
nil
|
||||
)
|
||||
|
||||
var enabledNotifications: Double = 0.0
|
||||
|
|
@ -459,10 +523,12 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
enabledNotifications = stats.enabledNotifications.value / stats.enabledNotifications.total
|
||||
}
|
||||
topRightItemLayoutAndApply = makeTopRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
String(format: "%.02f%%", enabledNotifications * 100.0),
|
||||
item.presentationData.strings.Stats_EnabledNotifications,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
|
|
@ -516,56 +582,68 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
|
||||
if let (value, title, delta) = items[0] {
|
||||
middle1LeftItemLayoutAndApply = makeMiddle1LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
value,
|
||||
title,
|
||||
delta
|
||||
delta,
|
||||
nil
|
||||
)
|
||||
}
|
||||
if let (value, title, delta) = items[1] {
|
||||
middle1RightItemLayoutAndApply = makeMiddle1RightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
value,
|
||||
title,
|
||||
delta
|
||||
delta,
|
||||
nil
|
||||
)
|
||||
}
|
||||
if let (value, title, delta) = items[2] {
|
||||
middle2LeftItemLayoutAndApply = makeMiddle2LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
value,
|
||||
title,
|
||||
delta
|
||||
delta,
|
||||
nil
|
||||
)
|
||||
}
|
||||
if let (value, title, delta) = items[3] {
|
||||
middle2RightItemLayoutAndApply = makeMiddle2RightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
value,
|
||||
title,
|
||||
delta
|
||||
delta,
|
||||
nil
|
||||
)
|
||||
}
|
||||
if let (value, title, delta) = items[4] {
|
||||
bottomLeftItemLayoutAndApply = makeBottomLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
value,
|
||||
title,
|
||||
delta
|
||||
delta,
|
||||
nil
|
||||
)
|
||||
}
|
||||
if let (value, title, delta) = items[5] {
|
||||
bottomRightItemLayoutAndApply = makeBottomRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
value,
|
||||
title,
|
||||
delta
|
||||
delta,
|
||||
nil
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -583,37 +661,45 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
|
||||
let membersDelta = deltaText(stats.members)
|
||||
topLeftItemLayoutAndApply = makeTopLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(Int(stats.members.current)),
|
||||
item.presentationData.strings.Stats_GroupMembers,
|
||||
(membersDelta.text, membersDelta.positive ? .positive : .negative)
|
||||
(membersDelta.text, membersDelta.positive ? .positive : .negative),
|
||||
nil
|
||||
)
|
||||
|
||||
let messagesDelta = deltaText(stats.messages)
|
||||
topRightItemLayoutAndApply = makeTopRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(Int(stats.messages.current)),
|
||||
item.presentationData.strings.Stats_GroupMessages,
|
||||
(messagesDelta.text, messagesDelta.positive ? .positive : .negative)
|
||||
(messagesDelta.text, messagesDelta.positive ? .positive : .negative),
|
||||
nil
|
||||
)
|
||||
|
||||
if displayBottomRow {
|
||||
middle1LeftItemLayoutAndApply = makeMiddle1LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(Int(stats.viewers.current)),
|
||||
item.presentationData.strings.Stats_GroupViewers,
|
||||
(viewersDelta.text, viewersDelta.positive ? .positive : .negative)
|
||||
(viewersDelta.text, viewersDelta.positive ? .positive : .negative),
|
||||
nil
|
||||
)
|
||||
|
||||
middle1RightItemLayoutAndApply = makeMiddle1RightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
compactNumericCountString(Int(stats.posters.current)),
|
||||
item.presentationData.strings.Stats_GroupPosters,
|
||||
(postersDelta.text, postersDelta.positive ? .positive : .negative)
|
||||
(postersDelta.text, postersDelta.positive ? .positive : .negative),
|
||||
nil
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -622,6 +708,40 @@ class StatsOverviewItemNode: ListViewItemNode {
|
|||
} else {
|
||||
height += topLeftItemLayoutAndApply!.0.height * 4.0 + verticalSpacing * 3.0
|
||||
}
|
||||
} else if let _ = item.stats as? MonetizationStats {
|
||||
twoColumnLayout = false
|
||||
|
||||
topLeftItemLayoutAndApply = makeTopLeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"54.12",
|
||||
"Balance Available to Withdraw",
|
||||
("≈$123", .generic),
|
||||
item.animatedEmoji
|
||||
)
|
||||
|
||||
topRightItemLayoutAndApply = makeTopRightItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"84.52",
|
||||
"Proceeds Since Last Withdrawal",
|
||||
("≈$226", .generic),
|
||||
item.animatedEmoji
|
||||
)
|
||||
|
||||
middle1LeftItemLayoutAndApply = makeMiddle1LeftItemLayout(
|
||||
item.context,
|
||||
params.width,
|
||||
item.presentationData,
|
||||
"692.52",
|
||||
"Total Lifetime Proceeds",
|
||||
("≈$1858", .generic),
|
||||
item.animatedEmoji
|
||||
)
|
||||
|
||||
height += topLeftItemLayoutAndApply!.0.height * 3.0 + verticalSpacing * 2.0
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: height)
|
||||
|
|
|
|||
475
submodules/StatisticsUI/Sources/TransactionInfoScreen.swift
Normal file
475
submodules/StatisticsUI/Sources/TransactionInfoScreen.swift
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Markdown
|
||||
import TextFormat
|
||||
import TelegramPresentationData
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import BundleIconComponent
|
||||
import BalancedTextComponent
|
||||
import MultilineTextComponent
|
||||
import SolidRoundedButtonComponent
|
||||
import LottieComponent
|
||||
import AccountContext
|
||||
import TelegramStringFormatting
|
||||
import PremiumPeerShortcutComponent
|
||||
|
||||
enum MonetizationTransaction: Equatable {
|
||||
case incoming(amount: Int64, fromTimestamp: Int32, toTimestamp: Int32)
|
||||
case outgoing(amount: Int64, timestamp: Int32, address: String, explorerUrl: String)
|
||||
|
||||
var amount: Int64 {
|
||||
switch self {
|
||||
case let .incoming(amount, _, _), let .outgoing(amount, _, _, _):
|
||||
return amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let transaction: MonetizationTransaction
|
||||
let openExplorer: (String) -> Void
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
transaction: MonetizationTransaction,
|
||||
openExplorer: @escaping (String) -> Void,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.transaction = transaction
|
||||
self.openExplorer = openExplorer
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.transaction != rhs.transaction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
|
||||
let playOnce = ActionSlot<Void>()
|
||||
private var didPlayAnimation = false
|
||||
|
||||
func playAnimationIfNeeded() {
|
||||
guard !self.didPlayAnimation else {
|
||||
return
|
||||
}
|
||||
self.didPlayAnimation = true
|
||||
self.playOnce.invoke(Void())
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State()
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let closeButton = Child(Button.self)
|
||||
|
||||
let amount = Child(MultilineTextComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let date = Child(MultilineTextComponent.self)
|
||||
let address = Child(MultilineTextComponent.self)
|
||||
let peerShortcut = Child(PremiumPeerShortcutComponent.self)
|
||||
|
||||
let actionButton = Child(SolidRoundedButtonComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let component = context.component
|
||||
let state = context.state
|
||||
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat
|
||||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
let textSideInset: CGFloat = 32.0 + environment.safeInsets.left
|
||||
|
||||
let titleFont = Font.semibold(17.0)
|
||||
let textFont = Font.regular(17.0)
|
||||
let fixedFont = Font.monospace(17.0)
|
||||
|
||||
let textColor = theme.actionSheet.primaryTextColor
|
||||
let secondaryTextColor = theme.actionSheet.secondaryTextColor
|
||||
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 45.0)
|
||||
|
||||
let closeImage: UIImage
|
||||
if let (image, theme) = state.cachedCloseImage, theme === environment.theme {
|
||||
closeImage = image
|
||||
} else {
|
||||
closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)!
|
||||
state.cachedCloseImage = (closeImage, theme)
|
||||
}
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
component: Button(
|
||||
content: AnyComponent(Image(image: closeImage)),
|
||||
action: { [weak component] in
|
||||
component?.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0))
|
||||
)
|
||||
|
||||
let amountString: NSMutableAttributedString
|
||||
let dateString: String
|
||||
let titleString: String
|
||||
let subtitleString: String
|
||||
let buttonTitle: String
|
||||
let explorerUrl: String?
|
||||
|
||||
let integralFont = Font.with(size: 48.0, design: .round, weight: .semibold)
|
||||
let fractionalFont = Font.with(size: 24.0, design: .round, weight: .semibold)
|
||||
|
||||
//TODO:localize
|
||||
switch component.transaction {
|
||||
case let .incoming(amount, fromTimestamp, toTimestamp):
|
||||
amountString = amountAttributedString(formatBalanceText(amount, decimalSeparator: dateTimeFormat.decimalSeparator, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: theme.list.itemDisclosureActions.constructive.fillColor).mutableCopy() as! NSMutableAttributedString
|
||||
amountString.append(NSAttributedString(string: " TON", font: fractionalFont, textColor: theme.list.itemDisclosureActions.constructive.fillColor))
|
||||
dateString = "\(stringForFullDate(timestamp: fromTimestamp, strings: strings, dateTimeFormat: dateTimeFormat)) – \(stringForFullDate(timestamp: toTimestamp, strings: strings, dateTimeFormat: dateTimeFormat))"
|
||||
titleString = "Proceeds from Ads displayed in"
|
||||
subtitleString = ""
|
||||
buttonTitle = strings.Common_OK
|
||||
explorerUrl = nil
|
||||
case let .outgoing(amount, timestamp, address, explorerUrlValue):
|
||||
amountString = amountAttributedString(formatBalanceText(amount, decimalSeparator: dateTimeFormat.decimalSeparator), integralFont: integralFont, fractionalFont: fractionalFont, color: theme.list.itemDestructiveColor).mutableCopy() as! NSMutableAttributedString
|
||||
amountString.append(NSAttributedString(string: " TON", font: fractionalFont, textColor: theme.list.itemDestructiveColor))
|
||||
dateString = stringForFullDate(timestamp: timestamp, strings: strings, dateTimeFormat: dateTimeFormat)
|
||||
titleString = "Balance Withdrawal to"
|
||||
subtitleString = formatAddress(address)
|
||||
buttonTitle = "View in Blockchain Explorer"
|
||||
explorerUrl = explorerUrlValue
|
||||
}
|
||||
|
||||
let amount = amount.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(amountString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(amount
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + amount.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += amount.size.height
|
||||
contentSize.height += -5.0
|
||||
|
||||
let date = date.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: dateString, font: textFont, textColor: secondaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(date
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + date.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += date.size.height
|
||||
contentSize.height += 32.0
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleString, font: titleFont, textColor: textColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += 3.0
|
||||
|
||||
if !subtitleString.isEmpty {
|
||||
let address = address.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: subtitleString, font: fixedFont, textColor: textColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(address
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + address.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += address.size.height
|
||||
contentSize.height += 50.0
|
||||
} else {
|
||||
contentSize.height += 5.0
|
||||
let peerShortcut = peerShortcut.update(
|
||||
component: PremiumPeerShortcutComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
peer: component.peer
|
||||
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(peerShortcut
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + peerShortcut.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += peerShortcut.size.height
|
||||
contentSize.height += 50.0
|
||||
}
|
||||
|
||||
let actionButton = actionButton.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: buttonTitle,
|
||||
theme: SolidRoundedButtonComponent.Theme(
|
||||
backgroundColor: theme.list.itemCheckColors.fillColor,
|
||||
backgroundColors: [],
|
||||
foregroundColor: theme.list.itemCheckColors.foregroundColor
|
||||
),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: false,
|
||||
iconName: nil,
|
||||
animationName: nil,
|
||||
iconPosition: .left,
|
||||
action: {
|
||||
component.dismiss()
|
||||
if let explorerUrl {
|
||||
component.openExplorer(explorerUrl)
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(actionButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += actionButton.size.height
|
||||
contentSize.height += 22.0
|
||||
|
||||
contentSize.height += environment.safeInsets.bottom
|
||||
|
||||
state.playAnimationIfNeeded()
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SheetContainerComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let transaction: MonetizationTransaction
|
||||
let openExplorer: (String) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
transaction: MonetizationTransaction,
|
||||
openExplorer: @escaping (String) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.transaction = transaction
|
||||
self.openExplorer = openExplorer
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContainerComponent, rhs: SheetContainerComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.transaction != rhs.transaction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(SheetComponent<EnvironmentType>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
peer: context.component.peer,
|
||||
transaction: context.component.transaction,
|
||||
openExplorer: context.component.openExplorer,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
externalState: sheetExternalState,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
|
||||
let layout = ContainerViewLayout(
|
||||
size: context.availableSize,
|
||||
metrics: environment.metrics,
|
||||
deviceMetrics: environment.deviceMetrics,
|
||||
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0),
|
||||
safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
|
||||
additionalInsets: .zero,
|
||||
statusBarHeight: environment.statusBarHeight,
|
||||
inputHeight: nil,
|
||||
inputHeightIsInteractivellyChanging: false,
|
||||
inVoiceOver: false
|
||||
)
|
||||
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
|
||||
}
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class TransactionInfoScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
transaction: MonetizationTransaction,
|
||||
openExplorer: @escaping (String) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: SheetContainerComponent(
|
||||
context: context,
|
||||
peer: peer,
|
||||
transaction: transaction,
|
||||
openExplorer: openExplorer
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
}
|
||||
|
||||
func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(foregroundColor.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/StickerPeekUI:StickerPeekUI",
|
||||
"//submodules/Pasteboard:Pasteboard",
|
||||
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController"
|
||||
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import AnimationCache
|
|||
import MultiAnimationRenderer
|
||||
import Pasteboard
|
||||
import StickerPackEditTitleController
|
||||
import EntityKeyboard
|
||||
|
||||
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool, isEditing: Bool, isAdd: Bool)
|
||||
|
|
@ -537,8 +538,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
if canEdit {
|
||||
menuItems.append(.action(ContextMenuActionItem(text: "Edit Sticker", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
if let _ = self {
|
||||
|
||||
if let self {
|
||||
self.openEditSticker(item.file)
|
||||
}
|
||||
})))
|
||||
if !strongSelf.isEditing {
|
||||
|
|
@ -1137,6 +1138,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.presentInGlobalOverlay(contextController, nil)
|
||||
}
|
||||
|
||||
private let stickerPickerInputData = Promise<StickerPickerInput>()
|
||||
private func presentAddStickerOptions() {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
|
|
@ -1155,9 +1157,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
controller.controllerNode.dismiss()
|
||||
|
||||
self.presentAddExistingSticker()
|
||||
controller.controllerNode.dismiss()
|
||||
}))
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
|
|
@ -1165,22 +1166,158 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
})
|
||||
])])
|
||||
self.presentInGlobalOverlay(actionSheet, nil)
|
||||
|
||||
|
||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(
|
||||
context: self.context,
|
||||
animationCache: self.context.animationCache,
|
||||
animationRenderer: self.context.animationRenderer,
|
||||
isStandalone: false,
|
||||
subject: .emoji,
|
||||
hasTrending: true,
|
||||
topReactionItems: [],
|
||||
areUnicodeEmojiEnabled: true,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: self.context.account.peerId,
|
||||
hasSearch: true,
|
||||
forceHasPremium: true
|
||||
)
|
||||
|
||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(
|
||||
context: self.context,
|
||||
animationCache: self.context.animationCache,
|
||||
animationRenderer: self.context.animationRenderer,
|
||||
stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks],
|
||||
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
||||
chatPeerId: self.context.account.peerId,
|
||||
hasSearch: true,
|
||||
hasTrending: true,
|
||||
forceHasPremium: true
|
||||
)
|
||||
|
||||
let signal = combineLatest(
|
||||
queue: .mainQueue(),
|
||||
emojiItems,
|
||||
stickerItems
|
||||
) |> map { emoji, stickers -> StickerPickerInput in
|
||||
return StickerPickerInputData(emoji: emoji, stickers: stickers, gifs: nil)
|
||||
}
|
||||
|
||||
self.stickerPickerInputData.set(signal)
|
||||
}
|
||||
|
||||
private func presentCreateSticker() {
|
||||
let controller = self.context.sharedContext.makeStickerMediaPickerScreen(
|
||||
context: self.context,
|
||||
guard let (info, _, _) = self.currentStickerPack else {
|
||||
return
|
||||
}
|
||||
let context = self.context
|
||||
let presentationData = self.presentationData
|
||||
let updatedPresentationData = self.controller?.updatedPresentationData
|
||||
let navigationController = self.controller?.parentNavigationController as? NavigationController
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
let mainController = context.sharedContext.makeStickerMediaPickerScreen(
|
||||
context: context,
|
||||
getSourceRect: { return .zero },
|
||||
completion: { result, transitionView, transitionRect, transitionImage, completion, dismissed in
|
||||
|
||||
let editorController = context.sharedContext.makeStickerEditorScreen(
|
||||
context: context,
|
||||
source: result,
|
||||
transitionArguments: (transitionView, transitionRect, transitionImage),
|
||||
completion: { file in
|
||||
dismissImpl?()
|
||||
let sticker = ImportSticker(
|
||||
resource: file.resource,
|
||||
emojis: ["😀"],
|
||||
dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512),
|
||||
mimeType: file.mimeType,
|
||||
keywords: ""
|
||||
)
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: "Sticker added to **\(info.title)** sticker set.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
navigationController?.pushViewController(editorController)
|
||||
},
|
||||
dismissed: {}
|
||||
)
|
||||
self.controller?.parentNavigationController?.pushViewController(controller)
|
||||
dismissImpl = { [weak mainController] in
|
||||
mainController?.dismiss()
|
||||
}
|
||||
navigationController?.pushViewController(mainController)
|
||||
}
|
||||
|
||||
private func presentAddExistingSticker() {
|
||||
guard let (info, _, _) = self.currentStickerPack else {
|
||||
return
|
||||
}
|
||||
let presentationData = self.presentationData
|
||||
let updatedPresentationData = self.controller?.updatedPresentationData
|
||||
let navigationController = self.controller?.parentNavigationController as? NavigationController
|
||||
|
||||
let context = self.context
|
||||
let controller = self.context.sharedContext.makeStickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData, completion: { file in
|
||||
let sticker = ImportSticker(
|
||||
resource: file.resource,
|
||||
emojis: ["😀"],
|
||||
dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512),
|
||||
mimeType: file.mimeType,
|
||||
keywords: ""
|
||||
)
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
packController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: "Sticker added to **\(info.title)** sticker set.", undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
})
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
|
||||
private func openEditSticker(_ initialFile: TelegramMediaFile) {
|
||||
guard let (info, _, _) = self.currentStickerPack else {
|
||||
return
|
||||
}
|
||||
let context = self.context
|
||||
let updatedPresentationData = self.controller?.updatedPresentationData
|
||||
let navigationController = self.controller?.parentNavigationController as? NavigationController
|
||||
|
||||
self.controller?.dismiss()
|
||||
|
||||
let controller = context.sharedContext.makeStickerEditorScreen(
|
||||
context: context,
|
||||
source: initialFile,
|
||||
transitionArguments: nil,
|
||||
completion: { file in
|
||||
let sticker = ImportSticker(
|
||||
resource: file.resource,
|
||||
emojis: ["😀"],
|
||||
dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512),
|
||||
mimeType: file.mimeType,
|
||||
keywords: ""
|
||||
)
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
|
||||
let _ = (context.engine.stickers.replaceSticker(previousSticker: .stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: initialFile), sticker: sticker)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
)
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
|
||||
private func presentEditPackTitle() {
|
||||
|
|
@ -2408,7 +2545,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
|||
}
|
||||
}
|
||||
|
||||
public final class StickerPackScreenImpl: ViewController {
|
||||
public final class StickerPackScreenImpl: ViewController, StickerPackScreen {
|
||||
private let context: AccountContext
|
||||
fileprivate var presentationData: PresentationData
|
||||
fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
|
|
|
|||
|
|
@ -526,7 +526,7 @@ if "".isEmpty {
|
|||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds)))
|
||||
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds)))
|
||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
switch result {
|
||||
|
|
@ -924,7 +924,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
|||
|> mapError { _ -> PendingMessageUploadError in }
|
||||
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
if let inputPeer = inputPeer {
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds)))
|
||||
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds)))
|
||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
switch result {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public func standaloneUploadedImage(postbox: Postbox, network: Network, peerId:
|
|||
|> mapError { _ -> StandaloneUploadMediaError in }
|
||||
|> mapToSignal { inputPeer -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
|
||||
if let inputPeer = inputPeer {
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: 0, file: inputFile, stickers: nil, ttlSeconds: nil)))
|
||||
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: 0, file: inputFile, stickers: nil, ttlSeconds: nil)))
|
||||
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|
||||
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
|
||||
switch media {
|
||||
|
|
@ -158,7 +158,7 @@ public func standaloneUploadedFile(postbox: Postbox, network: Network, peerId: P
|
|||
if let _ = thumbnailFile {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, ttlSeconds: nil)))
|
||||
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, ttlSeconds: nil)))
|
||||
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|
||||
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
|
||||
switch media {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||
var updatedTimestamp: Int32?
|
||||
if let apiMessage = apiMessage {
|
||||
switch apiMessage {
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
updatedTimestamp = date
|
||||
case .messageEmpty:
|
||||
break
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||
self.putNext(groups)
|
||||
}
|
||||
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
|
||||
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil)
|
||||
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil)
|
||||
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
||||
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||
if groups.count != 0 {
|
||||
|
|
@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||
|
||||
let generatedPeerId = Api.Peer.peerUser(userId: userId)
|
||||
|
||||
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil)
|
||||
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil)
|
||||
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
||||
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||
if groups.count != 0 {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ extension Api.MessageMedia {
|
|||
extension Api.Message {
|
||||
var rawId: Int32 {
|
||||
switch self {
|
||||
case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return id
|
||||
case let .messageEmpty(_, id, _):
|
||||
return id
|
||||
|
|
@ -115,7 +115,7 @@ extension Api.Message {
|
|||
|
||||
func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? {
|
||||
switch self {
|
||||
case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
let peerId: PeerId = messagePeerId.peerId
|
||||
return MessageId(peerId: peerId, namespace: namespace, id: id)
|
||||
case let .messageEmpty(_, id, peerId):
|
||||
|
|
@ -132,7 +132,7 @@ extension Api.Message {
|
|||
|
||||
var peerId: PeerId? {
|
||||
switch self {
|
||||
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
let peerId: PeerId = messagePeerId.peerId
|
||||
return peerId
|
||||
case let .messageEmpty(_, _, peerId):
|
||||
|
|
@ -145,7 +145,7 @@ extension Api.Message {
|
|||
|
||||
var timestamp: Int32? {
|
||||
switch self {
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return date
|
||||
case let .messageService(_, _, _, _, _, date, _, _):
|
||||
return date
|
||||
|
|
@ -156,7 +156,7 @@ extension Api.Message {
|
|||
|
||||
var preCachedResources: [(MediaResource, Data)]? {
|
||||
switch self {
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return media?.preCachedResources
|
||||
default:
|
||||
return nil
|
||||
|
|
@ -165,7 +165,7 @@ extension Api.Message {
|
|||
|
||||
var preCachedStories: [StoryId: Api.StoryItem]? {
|
||||
switch self {
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return media?.preCachedStories
|
||||
default:
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour
|
|||
var attributes: [Api.DocumentAttribute] = []
|
||||
attributes.append(.documentAttributeSticker(flags: 0, alt: alt, stickerset: .inputStickerSetEmpty, maskCoords: nil))
|
||||
attributes.append(.documentAttributeImageSize(w: dimensions.width, h: dimensions.height))
|
||||
return account.network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: nil, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil)))
|
||||
return account.network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: nil, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil)))
|
||||
|> mapError { _ -> UploadStickerError in return .generic }
|
||||
|> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in
|
||||
switch media {
|
||||
|
|
@ -384,6 +384,60 @@ func _internal_deleteStickerFromStickerSet(account: Account, sticker: FileMediaR
|
|||
}
|
||||
}
|
||||
|
||||
public enum ReplaceStickerError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_replaceSticker(account: Account, previousSticker: FileMediaReference, sticker: ImportSticker) -> Signal<Never, ReplaceStickerError> {
|
||||
guard let previousResource = previousSticker.media.resource as? CloudDocumentMediaResource else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
let uploadSticker: Signal<UploadStickerStatus, ReplaceStickerError>
|
||||
if let resource = sticker.resource as? CloudDocumentMediaResource {
|
||||
uploadSticker = .single(.complete(resource, sticker.mimeType))
|
||||
} else {
|
||||
uploadSticker = account.postbox.loadedPeerWithId(account.peerId)
|
||||
|> castError(ReplaceStickerError.self)
|
||||
|> mapToSignal { peer in
|
||||
return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType)
|
||||
|> mapError { _ -> ReplaceStickerError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
}
|
||||
return uploadSticker
|
||||
|> mapToSignal { uploadedSticker in
|
||||
guard case let .complete(resource, _) = uploadedSticker else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
if sticker.keywords.count > 0 {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords)
|
||||
|
||||
return account.network.request(Api.functions.stickers.replaceSticker(sticker: .inputDocument(id: previousResource.fileId, accessHash: previousResource.accessHash, fileReference: Buffer(data: previousResource.fileReference ?? Data())), newSticker: inputSticker))
|
||||
|> mapError { error -> ReplaceStickerError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, ReplaceStickerError> in
|
||||
guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else {
|
||||
return .complete()
|
||||
}
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if transaction.getItemCollectionInfo(collectionId: info.id) != nil {
|
||||
transaction.replaceItemCollectionItems(collectionId: info.id, items: items)
|
||||
}
|
||||
cacheStickerPack(transaction: transaction, info: info, items: items)
|
||||
}
|
||||
|> castError(ReplaceStickerError.self)
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_getMyStickerSets(account: Account) -> Signal<[(StickerPackCollectionInfo, StickerPackItem?)], NoError> {
|
||||
return account.network.request(Api.functions.messages.getMyStickers(offsetId: 0, limit: 100))
|
||||
|> map(Optional.init)
|
||||
|
|
|
|||
|
|
@ -106,6 +106,10 @@ public extension TelegramEngine {
|
|||
return _internal_deleteStickerFromStickerSet(account: self.account, sticker: sticker)
|
||||
}
|
||||
|
||||
public func replaceSticker(previousSticker: FileMediaReference, sticker: ImportSticker) -> Signal<Never, ReplaceStickerError> {
|
||||
return _internal_replaceSticker(account: self.account, previousSticker: previousSticker, sticker: sticker)
|
||||
}
|
||||
|
||||
public func getMyStickerSets() -> Signal<[(StickerPackCollectionInfo, StickerPackItem?)], NoError> {
|
||||
return _internal_getMyStickerSets(account: self.account)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,7 +437,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController",
|
||||
"//submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen"
|
||||
"//submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen",
|
||||
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
|
|
|||
|
|
@ -1615,6 +1615,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
break
|
||||
}
|
||||
}
|
||||
maximumContentWidth = max(0.0, maximumContentWidth)
|
||||
|
||||
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
|
||||
var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
bubbleInsets = layoutConstants.image.bubbleInsets
|
||||
}
|
||||
|
||||
sizeCalculation = .constrained(CGSize(width: constrainedSize.width - bubbleInsets.left - bubbleInsets.right, height: constrainedSize.height))
|
||||
sizeCalculation = .constrained(CGSize(width: max(0.0, constrainedSize.width - bubbleInsets.left - bubbleInsets.right), height: constrainedSize.height))
|
||||
case .mosaic:
|
||||
bubbleInsets = UIEdgeInsets()
|
||||
sizeCalculation = .unconstrained
|
||||
|
|
|
|||
|
|
@ -7538,3 +7538,19 @@ private final class FadingMaskLayer: SimpleLayer {
|
|||
self.fillLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: gradientHeight), size: CGSize(width: self.bounds.width, height: self.bounds.height - gradientHeight))
|
||||
}
|
||||
}
|
||||
|
||||
public struct StickerPickerInputData: StickerPickerInput, Equatable {
|
||||
public var emoji: EmojiPagerContentComponent
|
||||
public var stickers: EmojiPagerContentComponent?
|
||||
public var gifs: GifPagerContentComponent?
|
||||
|
||||
public init(
|
||||
emoji: EmojiPagerContentComponent,
|
||||
stickers: EmojiPagerContentComponent?,
|
||||
gifs: GifPagerContentComponent?
|
||||
) {
|
||||
self.emoji = emoji
|
||||
self.stickers = stickers
|
||||
self.gifs = gifs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ public final class MediaEditor {
|
|||
case asset(PHAsset)
|
||||
case draft(MediaEditorDraft)
|
||||
case message(MessageId)
|
||||
case sticker(TelegramMediaFile)
|
||||
|
||||
var dimensions: PixelDimensions {
|
||||
switch self {
|
||||
|
|
@ -117,6 +118,8 @@ public final class MediaEditor {
|
|||
return draft.dimensions
|
||||
case .message:
|
||||
return PixelDimensions(width: 1080, height: 1920)
|
||||
case .sticker:
|
||||
return PixelDimensions(width: 1080, height: 1920)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -654,6 +657,19 @@ public final class MediaEditor {
|
|||
)
|
||||
}
|
||||
}
|
||||
case .sticker:
|
||||
let image = generateImage(CGSize(width: 1080, height: 1920), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
}, opaque: false, scale: 1.0)
|
||||
textureSource = .single(
|
||||
TextureSourceResult(
|
||||
image: image,
|
||||
nightImage: nil,
|
||||
player: nil,
|
||||
playerIsReference: false,
|
||||
gradientColors: GradientColors(top: .clear, bottom: .clear)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
self.textureSourceDisposable = (textureSource
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ swift_library(
|
|||
"//submodules/WebPBinding",
|
||||
"//submodules/StickerResources",
|
||||
"//submodules/StickerPeekUI",
|
||||
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController"
|
||||
"//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController",
|
||||
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -193,6 +193,8 @@ extension MediaEditorScreen {
|
|||
if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) {
|
||||
innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920)))
|
||||
}
|
||||
case .sticker:
|
||||
break
|
||||
}
|
||||
|
||||
if case let .draft(draft, _) = actualSubject {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import WebPBinding
|
|||
import StickerResources
|
||||
import StickerPeekUI
|
||||
import StickerPackEditTitleController
|
||||
import StickerPickerScreen
|
||||
|
||||
private let playbackButtonTag = GenericComponentViewTag()
|
||||
private let muteButtonTag = GenericComponentViewTag()
|
||||
|
|
@ -2054,8 +2055,14 @@ let storyMaxVideoDuration: Double = 60.0
|
|||
|
||||
public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate {
|
||||
public enum Mode {
|
||||
public enum StickerEditorMode {
|
||||
case generic
|
||||
case addingToPack
|
||||
case editing
|
||||
}
|
||||
|
||||
case storyEditor
|
||||
case stickerEditor
|
||||
case stickerEditor(mode: StickerEditorMode)
|
||||
}
|
||||
|
||||
public enum TransitionIn {
|
||||
|
|
@ -2150,7 +2157,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
|
||||
let ciContext = CIContext(options: [.workingColorSpace : NSNull()])
|
||||
|
||||
private let stickerPickerInputData = Promise<StickerPickerInputData>()
|
||||
private let stickerPickerInputData = Promise<StickerPickerInput>()
|
||||
|
||||
fileprivate var availableReactions: [ReactionItem] = []
|
||||
private var availableReactionsDisposable: Disposable?
|
||||
|
|
@ -2211,8 +2218,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
self.stickerTransparentView = UIImageView()
|
||||
self.stickerTransparentView.clipsToBounds = true
|
||||
|
||||
var isStickerEditor = false
|
||||
if case .stickerEditor = controller.mode {
|
||||
isStickerEditor = true
|
||||
}
|
||||
|
||||
self.entitiesContainerView = UIView(frame: CGRect(origin: .zero, size: storyDimensions))
|
||||
self.entitiesView = DrawingEntitiesView(context: controller.context, size: storyDimensions, hasBin: true, isStickerEditor: controller.mode == .stickerEditor)
|
||||
self.entitiesView = DrawingEntitiesView(context: controller.context, size: storyDimensions, hasBin: true, isStickerEditor: isStickerEditor)
|
||||
self.entitiesView.getEntityCenterPosition = {
|
||||
return CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
|
||||
}
|
||||
|
|
@ -2316,7 +2328,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
queue: .mainQueue(),
|
||||
emojiItems,
|
||||
stickerItems
|
||||
) |> map { emoji, stickers -> StickerPickerInputData in
|
||||
) |> map { emoji, stickers -> StickerPickerInput in
|
||||
return StickerPickerInputData(emoji: emoji, stickers: stickers, gifs: nil)
|
||||
} |> afterNext { [weak self] _ in
|
||||
if let self {
|
||||
|
|
@ -2495,7 +2507,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
}
|
||||
}
|
||||
|
||||
let mediaEditor = MediaEditor(context: self.context, mode: controller.mode == .stickerEditor ? .sticker : .default, subject: effectiveSubject.editorSubject, values: initialValues, hasHistogram: true)
|
||||
var isStickerEditor = false
|
||||
if case .stickerEditor = controller.mode {
|
||||
isStickerEditor = true
|
||||
}
|
||||
|
||||
let mediaEditor = MediaEditor(context: self.context, mode: isStickerEditor ? .sticker : .default, subject: effectiveSubject.editorSubject, values: initialValues, hasHistogram: true)
|
||||
if let initialVideoPosition = controller.initialVideoPosition {
|
||||
mediaEditor.seek(initialVideoPosition, andPlay: true)
|
||||
}
|
||||
|
|
@ -2630,6 +2647,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
self.readyValue.set(.single(true))
|
||||
})
|
||||
})
|
||||
} else if case let .sticker(sticker) = effectiveSubject {
|
||||
let stickerEntity = DrawingStickerEntity(content: .file(sticker, .sticker))
|
||||
stickerEntity.referenceDrawingSize = storyDimensions
|
||||
stickerEntity.scale = 4.0
|
||||
stickerEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
|
||||
self.entitiesView.add(stickerEntity, announce: false)
|
||||
}
|
||||
|
||||
self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in
|
||||
|
|
@ -4066,7 +4089,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
if let controller = self.controller, case .stickerEditor = controller.mode {
|
||||
hasInteractiveStickers = false
|
||||
}
|
||||
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), defaultToEmoji: self.defaultToEmoji, hasGifs: hasInteractiveStickers, hasInteractiveStickers: hasInteractiveStickers)
|
||||
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), forceDark: true, defaultToEmoji: self.defaultToEmoji, hasGifs: hasInteractiveStickers, hasInteractiveStickers: hasInteractiveStickers)
|
||||
controller.completion = { [weak self] content in
|
||||
if let self {
|
||||
if let content {
|
||||
|
|
@ -4429,6 +4452,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
case asset(PHAsset)
|
||||
case draft(MediaEditorDraft, Int64?)
|
||||
case message([MessageId])
|
||||
case sticker(TelegramMediaFile)
|
||||
|
||||
var dimensions: PixelDimensions {
|
||||
switch self {
|
||||
|
|
@ -4440,6 +4464,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
return draft.dimensions
|
||||
case .message:
|
||||
return PixelDimensions(width: 1080, height: 1920)
|
||||
case .sticker:
|
||||
return PixelDimensions(width: 1080, height: 1920)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4455,6 +4481,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
return .draft(draft)
|
||||
case let .message(messageIds):
|
||||
return .message(messageIds.first!)
|
||||
case let .sticker(sticker):
|
||||
return .sticker(sticker)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4474,6 +4502,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
return draft.isVideo
|
||||
case .message:
|
||||
return false
|
||||
case .sticker:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5558,6 +5588,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
return (image, nil)
|
||||
}
|
||||
duration = 5.0
|
||||
case .sticker:
|
||||
let image = generateImage(CGSize(width: 1080, height: 1920), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
}, opaque: false, scale: 1.0)
|
||||
let tempImagePath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).png"
|
||||
if let data = image?.pngData() {
|
||||
try? data.write(to: URL(fileURLWithPath: tempImagePath))
|
||||
}
|
||||
videoResult = .single(.imageFile(path: tempImagePath))
|
||||
duration = 3.0
|
||||
|
||||
firstFrame = .single((image, nil))
|
||||
}
|
||||
|
||||
let _ = combineLatest(queue: Queue.mainQueue(), firstFrame, videoResult)
|
||||
|
|
@ -5672,10 +5714,133 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
}
|
||||
}
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
|
||||
let file = stickerFile(resource: resource, size: Int64(0), dimensions: PixelDimensions(image.size))
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
var menuItems: [ContextMenuItem] = []
|
||||
if case let .stickerEditor(mode) = self.mode {
|
||||
switch mode {
|
||||
case .generic:
|
||||
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.completion(MediaEditorScreen.Result(
|
||||
media: .sticker(file: file),
|
||||
mediaAreas: [],
|
||||
caption: NSAttributedString(),
|
||||
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
|
||||
stickers: [],
|
||||
randomId: 0
|
||||
), { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
Queue.mainQueue().justDispatch {
|
||||
finished()
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.uploadSticker(file, action: .addToFavorites)
|
||||
})))
|
||||
menuItems.append(.action(ContextMenuActionItem(text: "Add to Sticker Set", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var contextItems: [ContextMenuItem] = []
|
||||
contextItems.append(.action(ContextMenuActionItem(text: "Back", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||
}, iconPosition: .left, action: { c, _ in
|
||||
c.popItems()
|
||||
})))
|
||||
|
||||
contextItems.append(.separator)
|
||||
|
||||
contextItems.append(.action(ContextMenuActionItem(text: "New Sticker Set", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] _, f in
|
||||
if let self {
|
||||
self.presentCreateStickerPack(file: file, completion: {
|
||||
f(.default)
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
let thumbSize = CGSize(width: 24.0, height: 24.0)
|
||||
for (pack, firstItem) in self.myStickerPacks {
|
||||
let thumbnailResource = pack.thumbnail?.resource ?? firstItem?.file.resource
|
||||
let thumbnailIconSource: ContextMenuActionItemIconSource?
|
||||
if let thumbnailResource {
|
||||
var resourceId: Int64 = 0
|
||||
if let resource = thumbnailResource as? CloudDocumentMediaResource {
|
||||
resourceId = resource.fileId
|
||||
}
|
||||
let thumbnailFile = firstItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [])
|
||||
|
||||
let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start()
|
||||
thumbnailIconSource = ContextMenuActionItemIconSource(
|
||||
size: thumbSize,
|
||||
signal: chatMessageStickerPackThumbnail(postbox: self.context.account.postbox, resource: thumbnailResource)
|
||||
|> map { generator -> UIImage? in
|
||||
return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
thumbnailIconSource = nil
|
||||
}
|
||||
contextItems.append(.action(ContextMenuActionItem(text: pack.title, icon: { _ in return nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title))
|
||||
})))
|
||||
}
|
||||
|
||||
let items = ContextController.Items(
|
||||
id: 1,
|
||||
content: .list(contextItems),
|
||||
context: nil,
|
||||
reactionItems: [],
|
||||
selectedReactionItems: Set(),
|
||||
reactionsTitle: nil,
|
||||
reactionsLocked: false,
|
||||
animationCache: nil,
|
||||
alwaysAllowPremiumReactions: false,
|
||||
allPresetReactionsAreAvailable: false,
|
||||
getEmojiContent: nil,
|
||||
disablePositionLock: false,
|
||||
tip: nil,
|
||||
tipSignal: nil,
|
||||
dismissed: nil
|
||||
)
|
||||
c.pushItems(items: .single(items))
|
||||
})))
|
||||
case .editing:
|
||||
menuItems.append(.action(ContextMenuActionItem(text: "Replace Sticker", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
self.uploadSticker(file, action: .upload)
|
||||
})))
|
||||
case .addingToPack:
|
||||
menuItems.append(.action(ContextMenuActionItem(text: "Add to Sticker Set", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
self.uploadSticker(file, action: .upload)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
let peekController = PeekController(
|
||||
presentationData: presentationData,
|
||||
content: StickerPreviewPeekContent(
|
||||
|
|
@ -5684,108 +5849,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
strings: presentationData.strings,
|
||||
item: .image(image),
|
||||
isCreating: true,
|
||||
menu: [
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.completion(MediaEditorScreen.Result(
|
||||
media: .sticker(file: file),
|
||||
mediaAreas: [],
|
||||
caption: NSAttributedString(),
|
||||
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
|
||||
stickers: [],
|
||||
randomId: 0
|
||||
), { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
Queue.mainQueue().justDispatch {
|
||||
finished()
|
||||
}
|
||||
})
|
||||
})
|
||||
})),
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.uploadSticker(file, action: .addToFavorites)
|
||||
})),
|
||||
.action(ContextMenuActionItem(text: "Add to Sticker Set", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var contextItems: [ContextMenuItem] = []
|
||||
contextItems.append(.action(ContextMenuActionItem(text: "Back", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||
}, iconPosition: .left, action: { c, _ in
|
||||
c.popItems()
|
||||
})))
|
||||
|
||||
contextItems.append(.separator)
|
||||
|
||||
contextItems.append(.action(ContextMenuActionItem(text: "New Sticker Set", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { [weak self] _, f in
|
||||
if let self {
|
||||
self.presentCreateStickerPack(file: file, completion: {
|
||||
f(.default)
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
let thumbSize = CGSize(width: 24.0, height: 24.0)
|
||||
for (pack, firstItem) in self.myStickerPacks {
|
||||
let thumbnailResource = pack.thumbnail?.resource ?? firstItem?.file.resource
|
||||
let thumbnailIconSource: ContextMenuActionItemIconSource?
|
||||
if let thumbnailResource {
|
||||
var resourceId: Int64 = 0
|
||||
if let resource = thumbnailResource as? CloudDocumentMediaResource {
|
||||
resourceId = resource.fileId
|
||||
}
|
||||
let thumbnailFile = firstItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [])
|
||||
|
||||
let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start()
|
||||
thumbnailIconSource = ContextMenuActionItemIconSource(
|
||||
size: thumbSize,
|
||||
signal: chatMessageStickerPackThumbnail(postbox: self.context.account.postbox, resource: thumbnailResource)
|
||||
|> map { generator -> UIImage? in
|
||||
return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
thumbnailIconSource = nil
|
||||
}
|
||||
contextItems.append(.action(ContextMenuActionItem(text: pack.title, icon: { _ in return nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title))
|
||||
})))
|
||||
}
|
||||
|
||||
let items = ContextController.Items(
|
||||
id: 1,
|
||||
content: .list(contextItems),
|
||||
context: nil,
|
||||
reactionItems: [],
|
||||
selectedReactionItems: Set(),
|
||||
reactionsTitle: nil,
|
||||
reactionsLocked: false,
|
||||
animationCache: nil,
|
||||
alwaysAllowPremiumReactions: false,
|
||||
allPresetReactionsAreAvailable: false,
|
||||
getEmojiContent: nil,
|
||||
disablePositionLock: false,
|
||||
tip: nil,
|
||||
tipSignal: nil,
|
||||
dismissed: nil
|
||||
)
|
||||
c.pushItems(items: .single(items))
|
||||
}))
|
||||
],
|
||||
menu: menuItems,
|
||||
reactionItems: self.node.availableReactions,
|
||||
openPremiumIntro: {}
|
||||
),
|
||||
|
|
@ -5806,6 +5870,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
case addToFavorites
|
||||
case createStickerPack(title: String)
|
||||
case addToStickerPack(pack: StickerPackReference, title: String)
|
||||
case upload
|
||||
}
|
||||
|
||||
private func presentCreateStickerPack(file: TelegramMediaFile, completion: @escaping () -> Void) {
|
||||
|
|
@ -5938,6 +6003,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
|> map { _ in
|
||||
return status
|
||||
}
|
||||
case .upload:
|
||||
return .single(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5953,9 +6020,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
self.updateEditProgress(progress, cancel: { [weak self] in
|
||||
self?.stickerUploadDisposable.set(nil)
|
||||
})
|
||||
case .complete:
|
||||
case let .complete(resource, _):
|
||||
let navigationController = self.navigationController as? NavigationController
|
||||
self.completion(MediaEditorScreen.Result(), { [weak self] finished in
|
||||
|
||||
let result: MediaEditorScreen.Result
|
||||
if case .upload = action {
|
||||
let file = stickerFile(resource: resource, size: resource.size ?? 0, dimensions: dimensions)
|
||||
result = MediaEditorScreen.Result(
|
||||
media: .sticker(file: file),
|
||||
mediaAreas: [],
|
||||
caption: NSAttributedString(),
|
||||
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
|
||||
stickers: [],
|
||||
randomId: 0
|
||||
)
|
||||
} else {
|
||||
result = MediaEditorScreen.Result()
|
||||
}
|
||||
|
||||
self.completion(result, { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -5972,7 +6055,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
}
|
||||
case let .addToStickerPack(packReference, title):
|
||||
let navigationController = self.navigationController as? NavigationController
|
||||
self.dismiss()
|
||||
if let navigationController {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, parentNavigationController: nil, sendSticker: nil)
|
||||
|
|
@ -6102,6 +6184,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
return image.flatMap({ .single(.image(image: $0)) }) ?? .complete()
|
||||
}
|
||||
}
|
||||
case .sticker:
|
||||
let image = generateImage(CGSize(width: 1080, height: 1920), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
}, opaque: false, scale: 1.0)!
|
||||
exportSubject = .single(.image(image: image))
|
||||
}
|
||||
|
||||
let _ = exportSubject.start(next: { [weak self] exportSubject in
|
||||
|
|
|
|||
|
|
@ -8792,7 +8792,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen {
|
||||
previousController.dismiss()
|
||||
}
|
||||
let controller = self.context.sharedContext.makePremiumBoostLevelsController(context: self.context, peerId: peer.id, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: false, openStats: { [weak self] in
|
||||
let controller = self.context.sharedContext.makePremiumBoostLevelsController(context: self.context, peerId: peer.id, subject: .stories, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: false, openStats: { [weak self] in
|
||||
if let self {
|
||||
self.openStats(boosts: true, boostStatus: boostStatus)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PremiumPeerShortcutComponent",
|
||||
module_name = "PremiumPeerShortcutComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import AvatarNode
|
||||
import MultilineTextComponent
|
||||
|
||||
public final class PremiumPeerShortcutComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
|
||||
public init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
}
|
||||
|
||||
public static func ==(lhs: PremiumPeerShortcutComponent, rhs: PremiumPeerShortcutComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let backgroundView = UIView()
|
||||
private let avatarNode: AvatarNode
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private var component: PremiumPeerShortcutComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 18.0))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundView.clipsToBounds = true
|
||||
self.backgroundView.layer.cornerRadius = 16.0
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(component: PremiumPeerShortcutComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
self.backgroundView.backgroundColor = component.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3)
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: 30.0, height: 30.0))
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
peer: component.peer,
|
||||
synchronousLoad: true
|
||||
)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.medium(15.0), textColor: component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left))
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 50.0, height: availableSize.height)
|
||||
)
|
||||
|
||||
let size = CGSize(width: 30.0 + textSize.width + 20.0, height: 32.0)
|
||||
if let view = self.text.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
let textFrame = CGRect(origin: CGPoint(x: 38.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
view.frame = textFrame
|
||||
}
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -1111,7 +1111,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
self.hapticFeedback.impact(.light)
|
||||
} else {
|
||||
self.postingAvailabilityDisposable.set((component.context.engine.messages.checkStoriesUploadAvailability(target: .peer(peer.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
|
@ -1134,7 +1134,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen {
|
||||
previousController.dismiss()
|
||||
}
|
||||
let controller = component.context.sharedContext.makePremiumBoostLevelsController(context: component.context, peerId: peer.id, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: true, openStats: nil)
|
||||
let controller = component.context.sharedContext.makePremiumBoostLevelsController(context: component.context, peerId: peer.id, subject: .stories, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: true, openStats: nil)
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
self.hapticFeedback.impact(.light)
|
||||
|
|
|
|||
46
submodules/TelegramUI/Components/StickerPickerScreen/BUILD
Normal file
46
submodules/TelegramUI/Components/StickerPickerScreen/BUILD
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "StickerPickerScreen",
|
||||
module_name = "StickerPickerScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/FeaturedStickersScreen",
|
||||
"//submodules/Components/PagerComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboardGifContent",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/ChatPresentationInterfaceState",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/TelegramUI/Components/CameraButtonComponent",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/GalleryUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -17,28 +17,11 @@ import ChatEntityKeyboardInputNode
|
|||
import ContextUI
|
||||
import ChatPresentationInterfaceState
|
||||
import MediaEditor
|
||||
import StickerPackPreviewUI
|
||||
import EntityKeyboardGifContent
|
||||
import GalleryUI
|
||||
import UndoUI
|
||||
import CameraButtonComponent
|
||||
import BundleIconComponent
|
||||
|
||||
public struct StickerPickerInputData: Equatable {
|
||||
var emoji: EmojiPagerContentComponent
|
||||
var stickers: EmojiPagerContentComponent?
|
||||
var gifs: GifPagerContentComponent?
|
||||
|
||||
public init(
|
||||
emoji: EmojiPagerContentComponent,
|
||||
stickers: EmojiPagerContentComponent?,
|
||||
gifs: GifPagerContentComponent?
|
||||
) {
|
||||
self.emoji = emoji
|
||||
self.stickers = stickers
|
||||
self.gifs = gifs
|
||||
}
|
||||
}
|
||||
import UndoUI
|
||||
import GalleryUI
|
||||
|
||||
private final class StickerSelectionComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
|
@ -148,13 +131,13 @@ private final class StickerSelectionComponent: Component {
|
|||
sendSticker: { [weak self] file, silent, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, _ in
|
||||
if let self, let controller = self.component?.getController() {
|
||||
controller.forEachController { c in
|
||||
if let c = c as? StickerPackScreenImpl {
|
||||
if let c = c as? (ViewController & StickerPackScreen) {
|
||||
c.dismiss(animated: true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
controller.window?.forEachController({ c in
|
||||
if let c = c as? StickerPackScreenImpl {
|
||||
if let c = c as? (ViewController & StickerPackScreen) {
|
||||
c.dismiss(animated: true)
|
||||
}
|
||||
})
|
||||
|
|
@ -259,7 +242,8 @@ private final class StickerSelectionComponent: Component {
|
|||
|
||||
let topPanelHeight: CGFloat = 42.0
|
||||
|
||||
let defaultToEmoji = component.getController()?.defaultToEmoji ?? false
|
||||
let controller = component.getController()
|
||||
let defaultToEmoji = controller?.defaultToEmoji ?? false
|
||||
|
||||
let context = component.context
|
||||
let stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
|
||||
|
|
@ -360,7 +344,7 @@ private final class StickerSelectionComponent: Component {
|
|||
deviceMetrics: component.deviceMetrics,
|
||||
hiddenInputHeight: 0.0,
|
||||
inputHeight: 0.0,
|
||||
displayBottomPanel: true,
|
||||
displayBottomPanel: controller?.isFullscreen == false,
|
||||
isExpanded: true,
|
||||
clipContentToTopPanel: false,
|
||||
useExternalSearchContainer: false
|
||||
|
|
@ -523,7 +507,9 @@ public class StickerPickerScreen: ViewController {
|
|||
self.containerView.clipsToBounds = true
|
||||
self.containerView.backgroundColor = .clear
|
||||
|
||||
self.addSubnode(self.dim)
|
||||
if !controller.isFullscreen {
|
||||
self.addSubnode(self.dim)
|
||||
}
|
||||
|
||||
self.view.addSubview(self.wrappingView)
|
||||
self.wrappingView.addSubview(self.containerView)
|
||||
|
|
@ -646,12 +632,14 @@ public class StickerPickerScreen: ViewController {
|
|||
self.stickerSearchState.get(),
|
||||
self.emojiSearchState.get()
|
||||
)
|
||||
|
||||
|
||||
self.contentDisposable.set(data.start(next: { [weak self] inputData, gifData, stickerSearchState, emojiSearchState in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.presentationData
|
||||
var inputData = inputData
|
||||
|
||||
guard var inputData = inputData as? StickerPickerInputData else {
|
||||
return
|
||||
}
|
||||
|
||||
inputData.gifs = gifData?.component
|
||||
|
||||
let emoji = inputData.emoji
|
||||
|
|
@ -1544,7 +1532,9 @@ public class StickerPickerScreen: ViewController {
|
|||
|
||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
|
||||
self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
if let controller = self.controller, !controller.isFullscreen {
|
||||
controller.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
|
|
@ -1555,6 +1545,9 @@ public class StickerPickerScreen: ViewController {
|
|||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard let controller = self.controller, !controller.isFullscreen else {
|
||||
return false
|
||||
}
|
||||
if let (layout, _) = self.currentLayout {
|
||||
if layout.metrics.isTablet {
|
||||
return false
|
||||
|
|
@ -1579,6 +1572,9 @@ public class StickerPickerScreen: ViewController {
|
|||
|
||||
private var isDismissing = false
|
||||
func animateIn() {
|
||||
guard let controller = self.controller, !controller.isFullscreen else {
|
||||
return
|
||||
}
|
||||
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
|
||||
|
||||
let targetPosition = self.containerView.center
|
||||
|
|
@ -1643,7 +1639,10 @@ public class StickerPickerScreen: ViewController {
|
|||
|
||||
let clipFrame: CGRect
|
||||
let contentFrame: CGRect
|
||||
if layout.metrics.widthClass == .compact {
|
||||
if controller.isFullscreen {
|
||||
clipFrame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
contentFrame = clipFrame
|
||||
} else if layout.metrics.widthClass == .compact {
|
||||
self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25)
|
||||
if isLandscape {
|
||||
self.containerView.layer.cornerRadius = 0.0
|
||||
|
|
@ -1753,9 +1752,14 @@ public class StickerPickerScreen: ViewController {
|
|||
}
|
||||
|
||||
private var defaultTopInset: CGFloat {
|
||||
guard let (layout, _) = self.currentLayout else{
|
||||
guard let (layout, navigationBarHeight) = self.currentLayout else {
|
||||
return 210.0
|
||||
}
|
||||
|
||||
if let controller = self.controller, controller.isFullscreen {
|
||||
return navigationBarHeight
|
||||
}
|
||||
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
var factor: CGFloat = 0.2488
|
||||
if layout.size.width <= 320.0 {
|
||||
|
|
@ -1789,6 +1793,10 @@ public class StickerPickerScreen: ViewController {
|
|||
return
|
||||
}
|
||||
|
||||
guard let controller = self.controller, !controller.isFullscreen else {
|
||||
return
|
||||
}
|
||||
|
||||
let isLandscape = layout.orientation == .landscape
|
||||
let edgeTopInset = isLandscape ? 0.0 : defaultTopInset
|
||||
|
||||
|
|
@ -1970,8 +1978,10 @@ public class StickerPickerScreen: ViewController {
|
|||
|
||||
private let context: AccountContext
|
||||
private let theme: PresentationTheme
|
||||
private let inputData: Signal<StickerPickerInputData, NoError>
|
||||
private let inputData: Signal<StickerPickerInput, NoError>
|
||||
fileprivate let defaultToEmoji: Bool
|
||||
let isFullscreen: Bool
|
||||
let hasEmoji: Bool
|
||||
let hasGifs: Bool
|
||||
let hasInteractiveStickers: Bool
|
||||
|
||||
|
|
@ -1988,17 +1998,26 @@ public class StickerPickerScreen: ViewController {
|
|||
public var addReaction: () -> Void = { }
|
||||
public var addCamera: () -> Void = { }
|
||||
|
||||
public init(context: AccountContext, inputData: Signal<StickerPickerInputData, NoError>, defaultToEmoji: Bool = false, hasGifs: Bool = false, hasInteractiveStickers: Bool = true) {
|
||||
public init(context: AccountContext, inputData: Signal<StickerPickerInput, NoError>, forceDark: Bool = false, expanded: Bool = false, defaultToEmoji: Bool = false, hasEmoji: Bool = true, hasGifs: Bool = false, hasInteractiveStickers: Bool = true) {
|
||||
self.context = context
|
||||
self.theme = defaultDarkColorPresentationTheme
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme
|
||||
self.inputData = inputData
|
||||
self.isFullscreen = expanded
|
||||
self.defaultToEmoji = defaultToEmoji
|
||||
self.hasEmoji = hasEmoji
|
||||
self.hasGifs = hasGifs
|
||||
self.hasInteractiveStickers = hasInteractiveStickers
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
super.init(navigationBarPresentationData: expanded ? NavigationBarPresentationData(presentationData: presentationData) : nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
if expanded {
|
||||
//TODO:localize
|
||||
self.title = "Choose Sticker"
|
||||
self.navigationPresentation = .modal
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
|
|
@ -2014,14 +2033,18 @@ public class StickerPickerScreen: ViewController {
|
|||
|
||||
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
self.view.endEditing(true)
|
||||
if flag {
|
||||
self.node.animateOut(completion: {
|
||||
if self.isFullscreen {
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
if flag {
|
||||
self.node.animateOut(completion: {
|
||||
super.dismiss(animated: false, completion: {})
|
||||
completion?()
|
||||
})
|
||||
} else {
|
||||
super.dismiss(animated: false, completion: {})
|
||||
completion?()
|
||||
})
|
||||
} else {
|
||||
super.dismiss(animated: false, completion: {})
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -21,10 +21,12 @@ public final class TabSelectorComponent: Component {
|
|||
public struct CustomLayout: Equatable {
|
||||
public var font: UIFont
|
||||
public var spacing: CGFloat
|
||||
public var lineSelection: Bool
|
||||
|
||||
public init(font: UIFont, spacing: CGFloat) {
|
||||
public init(font: UIFont, spacing: CGFloat, lineSelection: Bool = false) {
|
||||
self.font = font
|
||||
self.spacing = spacing
|
||||
self.lineSelection = lineSelection
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +109,8 @@ public final class TabSelectorComponent: Component {
|
|||
}
|
||||
|
||||
func update(component: TabSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let selectionColorUpdated = component.colors.selection != self.component?.colors.selection
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
|
|
@ -115,14 +119,27 @@ public final class TabSelectorComponent: Component {
|
|||
let spacing: CGFloat = component.customLayout?.spacing ?? 2.0
|
||||
|
||||
let itemFont: UIFont
|
||||
var isLineSelection = false
|
||||
if let customLayout = component.customLayout {
|
||||
itemFont = customLayout.font
|
||||
isLineSelection = customLayout.lineSelection
|
||||
} else {
|
||||
itemFont = Font.semibold(14.0)
|
||||
}
|
||||
|
||||
if self.selectionView.image == nil {
|
||||
self.selectionView.image = generateStretchableFilledCircleImage(diameter: baseHeight, color: component.colors.selection)
|
||||
if selectionColorUpdated {
|
||||
if isLineSelection {
|
||||
self.selectionView.image = generateImage(CGSize(width: 5.0, height: 3.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(component.colors.selection.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: 4.0, height: 4.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 4.0, y: 0.0), size: CGSize(width: 4.0, height: 4.0)))
|
||||
context.fill(CGRect(x: 2.0, y: 0.0, width: size.width - 4.0, height: 4.0))
|
||||
context.fill(CGRect(x: 0.0, y: 2.0, width: size.width, height: 2.0))
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 3.0, left: 3.0, bottom: 0.0, right: 3.0), resizingMode: .stretch)
|
||||
} else {
|
||||
self.selectionView.image = generateStretchableFilledCircleImage(diameter: baseHeight, color: component.colors.selection)
|
||||
}
|
||||
}
|
||||
|
||||
var contentWidth: CGFloat = 0.0
|
||||
|
|
@ -146,7 +163,11 @@ public final class TabSelectorComponent: Component {
|
|||
let itemSize = itemView.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(Text(text: item.title, font: itemFont, color: component.colors.foreground)),
|
||||
content: AnyComponent(Text(
|
||||
text: item.title,
|
||||
font: itemFont,
|
||||
color: item.id == component.selectedId && isLineSelection ? component.colors.selection : component.colors.foreground
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: nil,
|
||||
action: { [weak self] in
|
||||
|
|
@ -154,7 +175,8 @@ public final class TabSelectorComponent: Component {
|
|||
return
|
||||
}
|
||||
component.setSelectedId(itemId)
|
||||
}
|
||||
},
|
||||
animateScale: !isLineSelection
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
|
|
@ -178,7 +200,7 @@ public final class TabSelectorComponent: Component {
|
|||
}
|
||||
itemTransition.setPosition(view: itemTitleView, position: itemTitleFrame.origin)
|
||||
itemTransition.setBounds(view: itemTitleView, bounds: CGRect(origin: CGPoint(), size: itemTitleFrame.size))
|
||||
itemTransition.setAlpha(view: itemTitleView, alpha: item.id == component.selectedId ? 1.0 : 0.4)
|
||||
itemTransition.setAlpha(view: itemTitleView, alpha: item.id == component.selectedId || isLineSelection ? 1.0 : 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +217,15 @@ public final class TabSelectorComponent: Component {
|
|||
|
||||
if let selectedBackgroundRect {
|
||||
self.selectionView.alpha = 1.0
|
||||
transition.setFrame(view: self.selectionView, frame: selectedBackgroundRect)
|
||||
|
||||
if isLineSelection {
|
||||
var mappedSelectionFrame = selectedBackgroundRect.insetBy(dx: 12.0, dy: 0.0)
|
||||
mappedSelectionFrame.origin.y = mappedSelectionFrame.maxY + 6.0
|
||||
mappedSelectionFrame.size.height = 3.0
|
||||
transition.setFrame(view: self.selectionView, frame: mappedSelectionFrame)
|
||||
} else {
|
||||
transition.setFrame(view: self.selectionView, frame: selectedBackgroundRect)
|
||||
}
|
||||
} else {
|
||||
self.selectionView.alpha = 0.0
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chart/Ads.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chart/Ads.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ads_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
99
submodules/TelegramUI/Images.xcassets/Chart/Ads.imageset/ads_30.pdf
vendored
Normal file
99
submodules/TelegramUI/Images.xcassets/Chart/Ads.imageset/ads_30.pdf
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.334961 4.678772 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
18.330002 19.142712 m
|
||||
18.330002 20.972393 16.201080 21.977081 14.788693 20.813940 c
|
||||
8.926420 15.986186 l
|
||||
4.665000 15.986186 l
|
||||
2.088591 15.986186 0.000000 13.897594 0.000000 11.321186 c
|
||||
0.000000 8.970550 1.738582 7.025980 4.000000 6.703224 c
|
||||
4.000000 3.821186 l
|
||||
4.000000 2.349346 5.193161 1.156185 6.665000 1.156185 c
|
||||
8.136839 1.156185 9.330000 2.349346 9.330000 3.821186 c
|
||||
9.330000 6.323827 l
|
||||
14.788692 1.828432 l
|
||||
16.201078 0.665291 18.330002 1.669977 18.330002 3.499660 c
|
||||
18.330002 19.142712 l
|
||||
h
|
||||
8.000000 6.656186 m
|
||||
5.330000 6.656186 l
|
||||
5.330000 3.821186 l
|
||||
5.330000 3.083885 5.927700 2.486187 6.665000 2.486187 c
|
||||
7.402300 2.486187 8.000000 3.083885 8.000000 3.821186 c
|
||||
8.000000 6.656186 l
|
||||
h
|
||||
15.634185 19.787273 m
|
||||
16.178917 20.235874 17.000000 19.848385 17.000000 19.142712 c
|
||||
17.000000 3.499660 l
|
||||
17.000000 2.793987 16.178915 2.406498 15.634184 2.855101 c
|
||||
9.623762 7.804859 l
|
||||
9.481418 7.922084 9.302749 7.986186 9.118342 7.986186 c
|
||||
4.665000 7.986186 l
|
||||
2.823130 7.986186 1.330000 9.479317 1.330000 11.321186 c
|
||||
1.330000 13.163055 2.823130 14.656186 4.665000 14.656186 c
|
||||
9.118342 14.656186 l
|
||||
9.302745 14.656186 9.481414 14.720285 9.623762 14.837513 c
|
||||
15.634185 19.787273 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1297
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001387 00000 n
|
||||
0000001410 00000 n
|
||||
0000001583 00000 n
|
||||
0000001657 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1716
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Chart/Monetization.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chart/Monetization.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "monetization_90.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
188
submodules/TelegramUI/Images.xcassets/Chart/Monetization.imageset/monetization_90.pdf
vendored
Normal file
188
submodules/TelegramUI/Images.xcassets/Chart/Monetization.imageset/monetization_90.pdf
vendored
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 23.000000 17.500000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
37.500000 41.500000 m
|
||||
37.500000 33.215729 30.784271 26.500000 22.500000 26.500000 c
|
||||
14.215729 26.500000 7.500000 33.215729 7.500000 41.500000 c
|
||||
7.500000 49.784271 14.215729 56.500000 22.500000 56.500000 c
|
||||
30.784271 56.500000 37.500000 49.784271 37.500000 41.500000 c
|
||||
h
|
||||
16.055553 45.507233 m
|
||||
15.908934 45.773811 16.101799 46.099998 16.406040 46.099998 c
|
||||
21.299994 46.099998 l
|
||||
21.299994 35.971886 l
|
||||
16.055553 45.507233 l
|
||||
h
|
||||
23.699993 35.971840 m
|
||||
28.944456 45.507229 l
|
||||
29.091078 45.773815 28.898209 46.099998 28.593971 46.099998 c
|
||||
23.699993 46.099998 l
|
||||
23.699993 35.971840 l
|
||||
h
|
||||
16.406040 48.500000 m
|
||||
14.276352 48.500000 12.926297 46.216698 13.952635 44.350628 c
|
||||
20.046600 33.270691 l
|
||||
21.110390 31.336523 23.889614 31.336515 24.953409 33.270691 c
|
||||
31.047375 44.350624 l
|
||||
32.073708 46.216690 30.723663 48.500000 28.593971 48.500000 c
|
||||
16.406040 48.500000 l
|
||||
h
|
||||
4.837455 37.573509 m
|
||||
5.430337 36.994904 5.441912 36.045227 4.863308 35.452347 c
|
||||
3.594926 34.152664 3.000000 32.810127 3.000000 31.500000 c
|
||||
3.000000 29.437628 4.521029 27.238575 7.690794 25.410986 c
|
||||
7.902112 25.289146 8.120757 25.168959 8.346768 25.050571 c
|
||||
11.891893 23.193604 16.893353 22.000000 22.500000 22.000000 c
|
||||
28.106646 22.000000 33.108109 23.193604 36.653233 25.050571 c
|
||||
40.269405 26.944759 42.000000 29.300137 42.000000 31.500000 c
|
||||
42.000000 32.810127 41.405075 34.152664 40.136692 35.452347 c
|
||||
39.558090 36.045227 39.569664 36.994904 40.162544 37.573509 c
|
||||
40.755428 38.152111 41.705105 38.140537 42.283710 37.547653 c
|
||||
43.943344 35.847065 45.000000 33.790764 45.000000 31.500000 c
|
||||
45.000000 22.500000 l
|
||||
45.000000 21.500000 l
|
||||
45.000000 12.500000 l
|
||||
45.000000 8.624733 42.029583 5.480110 38.045254 3.393078 c
|
||||
33.989872 1.268829 28.491333 0.000000 22.500000 0.000000 c
|
||||
16.508667 0.000000 11.010127 1.268829 6.954747 3.393078 c
|
||||
2.970417 5.480110 0.000000 8.624733 0.000000 12.500000 c
|
||||
0.000000 21.500000 l
|
||||
0.000000 22.500000 l
|
||||
0.000000 31.500000 l
|
||||
0.000000 33.790764 1.056656 35.847065 2.716292 37.547653 c
|
||||
3.294897 38.140537 4.244574 38.152111 4.837455 37.573509 c
|
||||
h
|
||||
38.045254 22.393078 m
|
||||
39.540852 23.176487 40.893585 24.108913 41.999977 25.173065 c
|
||||
42.000000 25.173086 l
|
||||
42.000000 22.500000 l
|
||||
42.000000 21.500000 l
|
||||
42.000000 19.371666 40.380116 17.097767 37.000000 15.236786 c
|
||||
37.000000 21.879204 l
|
||||
37.358109 22.044373 37.706707 22.215744 38.045254 22.393078 c
|
||||
h
|
||||
34.000000 20.713173 m
|
||||
32.442459 20.208160 30.765238 19.804638 29.000000 19.516380 c
|
||||
29.000000 12.559616 l
|
||||
30.805933 12.881241 32.486198 13.331841 34.000000 13.885990 c
|
||||
34.000000 20.713173 l
|
||||
h
|
||||
22.500000 19.000000 m
|
||||
23.688532 19.000000 24.857672 19.049931 26.000000 19.146568 c
|
||||
26.000000 12.158058 l
|
||||
24.866440 12.054405 23.697014 12.000000 22.500000 12.000000 c
|
||||
21.651834 12.000000 20.817518 12.027317 20.000000 12.080112 c
|
||||
20.000000 19.074383 l
|
||||
20.821783 19.025181 21.656002 19.000000 22.500000 19.000000 c
|
||||
h
|
||||
22.500000 9.000000 m
|
||||
23.688532 9.000000 24.857672 9.049931 26.000000 9.146568 c
|
||||
26.000000 3.158058 l
|
||||
24.866440 3.054405 23.697014 3.000000 22.500000 3.000000 c
|
||||
21.651834 3.000000 20.817518 3.027317 20.000000 3.080112 c
|
||||
20.000000 9.074383 l
|
||||
20.821783 9.025181 21.656002 9.000000 22.500000 9.000000 c
|
||||
h
|
||||
34.000000 4.885990 m
|
||||
32.486198 4.331841 30.805933 3.881241 29.000000 3.559616 c
|
||||
29.000000 9.516380 l
|
||||
30.765238 9.804638 32.442459 10.208160 34.000000 10.713173 c
|
||||
34.000000 4.885990 l
|
||||
h
|
||||
17.000000 12.396725 m
|
||||
17.000000 19.366760 l
|
||||
14.866652 19.657177 12.848998 20.113659 11.000000 20.713173 c
|
||||
11.000000 13.885990 l
|
||||
12.789782 13.230812 14.812259 12.720390 17.000000 12.396725 c
|
||||
h
|
||||
17.000000 9.366760 m
|
||||
17.000000 3.396725 l
|
||||
14.812259 3.720390 12.789782 4.230812 11.000000 4.885990 c
|
||||
11.000000 10.713173 l
|
||||
12.848998 10.113659 14.866652 9.657177 17.000000 9.366760 c
|
||||
h
|
||||
6.954747 22.393078 m
|
||||
7.293292 22.215744 7.641894 22.044373 8.000000 21.879204 c
|
||||
8.000000 15.236786 l
|
||||
4.619881 17.097767 3.000000 19.371666 3.000000 21.500000 c
|
||||
3.000000 22.500000 l
|
||||
3.000000 25.173086 l
|
||||
3.003295 25.169918 l
|
||||
4.109081 24.107075 5.460623 23.175716 6.954747 22.393078 c
|
||||
h
|
||||
6.954747 12.393078 m
|
||||
7.293292 12.215744 7.641894 12.044373 8.000000 11.879204 c
|
||||
8.000000 6.236786 l
|
||||
4.619881 8.097767 3.000000 10.371666 3.000000 12.500000 c
|
||||
3.000000 15.173088 l
|
||||
4.106395 14.108929 5.459139 13.176491 6.954747 12.393078 c
|
||||
h
|
||||
37.000000 6.236786 m
|
||||
40.380116 8.097767 42.000000 10.371666 42.000000 12.500000 c
|
||||
42.000000 15.173088 l
|
||||
40.893604 14.108929 39.540863 13.176491 38.045254 12.393078 c
|
||||
37.706707 12.215744 37.358109 12.044373 37.000000 11.879204 c
|
||||
37.000000 6.236786 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4594
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 90.000000 90.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004684 00000 n
|
||||
0000004707 00000 n
|
||||
0000004880 00000 n
|
||||
0000004954 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
5013
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Chart/Split.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chart/Split.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "split_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
416
submodules/TelegramUI/Images.xcassets/Chart/Split.imageset/split_30.pdf
vendored
Normal file
416
submodules/TelegramUI/Images.xcassets/Chart/Split.imageset/split_30.pdf
vendored
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 2 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
0.923880 -0.382683 0.382683 0.923880 3.005669 4.533266 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
16.543879 16.094051 m
|
||||
16.482773 16.456203 16.139654 16.700247 15.777505 16.639141 c
|
||||
15.415355 16.578033 15.171310 16.234917 15.232417 15.872766 c
|
||||
16.543879 16.094051 l
|
||||
h
|
||||
13.983027 18.887110 m
|
||||
14.195807 18.587759 14.610972 18.517580 14.910323 18.730362 c
|
||||
15.209674 18.943142 15.279853 19.358307 15.067072 19.657660 c
|
||||
13.983027 18.887110 l
|
||||
h
|
||||
13.014956 21.709774 m
|
||||
12.715606 21.922554 12.300441 21.852375 12.087660 21.553024 c
|
||||
11.874879 21.253674 11.945057 20.838509 12.244409 20.625727 c
|
||||
13.014956 21.709774 l
|
||||
h
|
||||
9.230065 21.875116 m
|
||||
9.592216 21.814011 9.935332 22.058054 9.996439 22.420204 c
|
||||
10.057546 22.782354 9.813501 23.125473 9.451351 23.186579 c
|
||||
9.230065 21.875116 l
|
||||
h
|
||||
6.548648 23.186579 m
|
||||
6.186498 23.125473 5.942453 22.782354 6.003560 22.420204 c
|
||||
6.064667 22.058054 6.407784 21.814011 6.769934 21.875116 c
|
||||
6.548648 23.186579 l
|
||||
h
|
||||
3.755590 20.625727 m
|
||||
4.054941 20.838507 4.125120 21.253672 3.912338 21.553024 c
|
||||
3.699557 21.852375 3.284392 21.922554 2.985041 21.709772 c
|
||||
3.755590 20.625727 l
|
||||
h
|
||||
0.932927 19.657658 m
|
||||
0.720146 19.358307 0.790325 18.943140 1.089676 18.730360 c
|
||||
1.389027 18.517578 1.804192 18.587757 2.016973 18.887108 c
|
||||
0.932927 19.657658 l
|
||||
h
|
||||
0.767583 15.872766 m
|
||||
0.828690 16.234917 0.584645 16.578033 0.222495 16.639139 c
|
||||
-0.139655 16.700245 -0.482772 16.456202 -0.543879 16.094051 c
|
||||
0.767583 15.872766 l
|
||||
h
|
||||
-0.543879 13.191348 m
|
||||
-0.482772 12.829198 -0.139655 12.585154 0.222495 12.646260 c
|
||||
0.584646 12.707367 0.828690 13.050484 0.767583 13.412634 c
|
||||
-0.543879 13.191348 l
|
||||
h
|
||||
2.016974 10.398291 m
|
||||
1.804193 10.697641 1.389028 10.767819 1.089677 10.555038 c
|
||||
0.790326 10.342257 0.720147 9.927093 0.932928 9.627742 c
|
||||
2.016974 10.398291 l
|
||||
h
|
||||
2.985043 7.575627 m
|
||||
3.284394 7.362846 3.699559 7.433024 3.912340 7.732376 c
|
||||
4.125122 8.031727 4.054943 8.446892 3.755592 8.659673 c
|
||||
2.985043 7.575627 l
|
||||
h
|
||||
6.769935 7.410283 m
|
||||
6.407784 7.471390 6.064667 7.227345 6.003561 6.865195 c
|
||||
5.942454 6.503046 6.186498 6.159927 6.548648 6.098822 c
|
||||
6.769935 7.410283 l
|
||||
h
|
||||
9.451352 6.098822 m
|
||||
9.813502 6.159927 10.057547 6.503046 9.996440 6.865195 c
|
||||
9.935333 7.227345 9.592216 7.471390 9.230066 7.410283 c
|
||||
9.451352 6.098822 l
|
||||
h
|
||||
12.244410 8.659674 m
|
||||
11.945059 8.446893 11.874881 8.031728 12.087662 7.732377 c
|
||||
12.300443 7.433026 12.715608 7.362847 13.014958 7.575628 c
|
||||
12.244410 8.659674 l
|
||||
h
|
||||
15.067073 9.627744 m
|
||||
15.279854 9.927094 15.209676 10.342259 14.910324 10.555040 c
|
||||
14.610973 10.767821 14.195808 10.697643 13.983027 10.398292 c
|
||||
15.067073 9.627744 l
|
||||
h
|
||||
15.232417 13.412635 m
|
||||
15.171310 13.050485 15.415355 12.707368 15.777505 12.646261 c
|
||||
16.139654 12.585155 16.482773 12.829199 16.543879 13.191349 c
|
||||
15.232417 13.412635 l
|
||||
h
|
||||
16.665001 14.642700 m
|
||||
16.665001 15.136786 16.623579 15.621709 16.543879 16.094051 c
|
||||
15.232417 15.872766 l
|
||||
15.299830 15.473239 15.335000 15.062332 15.335000 14.642700 c
|
||||
16.665001 14.642700 l
|
||||
h
|
||||
15.067072 19.657660 m
|
||||
14.502963 20.451275 13.808573 21.145664 13.014956 21.709774 c
|
||||
12.244409 20.625727 l
|
||||
12.916721 20.147842 13.505140 19.559423 13.983027 18.887110 c
|
||||
15.067072 19.657660 l
|
||||
h
|
||||
9.451351 23.186579 m
|
||||
8.979008 23.266279 8.494085 23.307701 8.000000 23.307701 c
|
||||
8.000000 21.977699 l
|
||||
8.419632 21.977699 8.830539 21.942530 9.230065 21.875116 c
|
||||
9.451351 23.186579 l
|
||||
h
|
||||
8.000000 23.307701 m
|
||||
7.505914 23.307701 7.020991 23.266279 6.548648 23.186579 c
|
||||
6.769934 21.875116 l
|
||||
7.169461 21.942530 7.580368 21.977699 8.000000 21.977699 c
|
||||
8.000000 23.307701 l
|
||||
h
|
||||
2.985041 21.709772 m
|
||||
2.191425 21.145662 1.497036 20.451273 0.932927 19.657658 c
|
||||
2.016973 18.887108 l
|
||||
2.494858 19.559422 3.083277 20.147840 3.755590 20.625727 c
|
||||
2.985041 21.709772 l
|
||||
h
|
||||
-0.543879 16.094051 m
|
||||
-0.623579 15.621708 -0.665000 15.136786 -0.665000 14.642700 c
|
||||
0.665000 14.642700 l
|
||||
0.665000 15.062332 0.700170 15.473238 0.767583 15.872766 c
|
||||
-0.543879 16.094051 l
|
||||
h
|
||||
-0.665000 14.642700 m
|
||||
-0.665000 14.148614 -0.623578 13.663692 -0.543879 13.191348 c
|
||||
0.767583 13.412634 l
|
||||
0.700170 13.812161 0.665000 14.223067 0.665000 14.642700 c
|
||||
-0.665000 14.642700 l
|
||||
h
|
||||
0.932928 9.627742 m
|
||||
1.497037 8.834126 2.191427 8.139736 2.985043 7.575627 c
|
||||
3.755592 8.659673 l
|
||||
3.083278 9.137559 2.494860 9.725977 2.016974 10.398291 c
|
||||
0.932928 9.627742 l
|
||||
h
|
||||
6.548648 6.098822 m
|
||||
7.020992 6.019121 7.505915 5.977699 8.000000 5.977699 c
|
||||
8.000000 7.307700 l
|
||||
7.580368 7.307700 7.169462 7.342870 6.769935 7.410283 c
|
||||
6.548648 6.098822 l
|
||||
h
|
||||
8.000000 5.977699 m
|
||||
8.494086 5.977699 8.979009 6.019121 9.451352 6.098822 c
|
||||
9.230066 7.410283 l
|
||||
8.830539 7.342870 8.419633 7.307700 8.000000 7.307700 c
|
||||
8.000000 5.977699 l
|
||||
h
|
||||
13.014958 7.575628 m
|
||||
13.808575 8.139737 14.502964 8.834127 15.067073 9.627744 c
|
||||
13.983027 10.398292 l
|
||||
13.505141 9.725979 12.916723 9.137560 12.244410 8.659674 c
|
||||
13.014958 7.575628 l
|
||||
h
|
||||
16.543879 13.191349 m
|
||||
16.623579 13.663692 16.665001 14.148615 16.665001 14.642700 c
|
||||
15.335000 14.642700 l
|
||||
15.335000 14.223068 15.299830 13.812161 15.232417 13.412635 c
|
||||
16.543879 13.191349 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
4945
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 4 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 16.000000 5.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 20.000000 m
|
||||
10.000000 20.000000 l
|
||||
10.000000 0.000000 l
|
||||
0.000000 0.000000 l
|
||||
0.000000 20.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
233
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /XObject << /X1 1 0 R >>
|
||||
/ExtGState << /E1 << /SMask << /Type /Mask
|
||||
/G 3 0 R
|
||||
/S /Alpha
|
||||
>>
|
||||
/Type /ExtGState
|
||||
>> >>
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Length 7 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
/X1 Do
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.000122 3.579651 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.001400 1.469524 m
|
||||
8.935791 0.807770 l
|
||||
9.001400 1.469524 l
|
||||
h
|
||||
9.067010 2.131281 m
|
||||
4.349816 2.598965 0.665000 6.579719 0.665000 11.420288 c
|
||||
-0.665000 11.420288 l
|
||||
-0.665000 5.889203 3.544995 1.342237 8.935791 0.807770 c
|
||||
9.067010 2.131281 l
|
||||
h
|
||||
0.665000 11.420288 m
|
||||
0.665000 16.260859 4.349816 20.241613 9.067010 20.709297 c
|
||||
8.935791 22.032806 l
|
||||
3.544996 21.498341 -0.665000 16.951374 -0.665000 11.420288 c
|
||||
0.665000 11.420288 l
|
||||
h
|
||||
9.335000 20.420288 m
|
||||
9.335000 2.420288 l
|
||||
10.665000 2.420288 l
|
||||
10.665000 20.420288 l
|
||||
9.335000 20.420288 l
|
||||
h
|
||||
9.067010 20.709297 m
|
||||
9.134297 20.715967 9.193896 20.694256 9.243861 20.645805 c
|
||||
9.296615 20.594650 9.335000 20.515173 9.335000 20.420288 c
|
||||
10.665000 20.420288 l
|
||||
10.665000 21.320156 9.920688 22.130455 8.935791 22.032806 c
|
||||
9.067010 20.709297 l
|
||||
h
|
||||
8.935791 0.807770 m
|
||||
9.920687 0.710121 10.665000 1.520422 10.665000 2.420288 c
|
||||
9.335000 2.420288 l
|
||||
9.335000 2.325403 9.296615 2.245926 9.243861 2.194773 c
|
||||
9.193896 2.146320 9.134296 2.124609 9.067010 2.131281 c
|
||||
8.935791 0.807770 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.250000 13.790161 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.720226 10.239613 m
|
||||
9.979925 10.499311 9.979925 10.920366 9.720226 11.180065 c
|
||||
9.460527 11.439764 9.039473 11.439764 8.779774 11.180065 c
|
||||
9.720226 10.239613 l
|
||||
h
|
||||
-0.470226 1.930065 m
|
||||
-0.729925 1.670366 -0.729925 1.249311 -0.470226 0.989613 c
|
||||
-0.210527 0.729914 0.210527 0.729914 0.470226 0.989613 c
|
||||
-0.470226 1.930065 l
|
||||
h
|
||||
8.779774 11.180065 m
|
||||
-0.470226 1.930065 l
|
||||
0.470226 0.989613 l
|
||||
9.720226 10.239613 l
|
||||
8.779774 11.180065 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.000000 9.540161 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.470226 9.989613 m
|
||||
9.729925 10.249311 9.729925 10.670366 9.470226 10.930065 c
|
||||
9.210527 11.189764 8.789473 11.189764 8.529774 10.930065 c
|
||||
9.470226 9.989613 l
|
||||
h
|
||||
-0.470226 1.930065 m
|
||||
-0.729925 1.670366 -0.729925 1.249311 -0.470226 0.989613 c
|
||||
-0.210527 0.729914 0.210527 0.729914 0.470226 0.989613 c
|
||||
-0.470226 1.930065 l
|
||||
h
|
||||
8.529774 10.930065 m
|
||||
-0.470226 1.930065 l
|
||||
0.470226 0.989613 l
|
||||
9.470226 9.989613 l
|
||||
8.529774 10.930065 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 8.500000 7.040161 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
6.970226 7.489613 m
|
||||
7.229925 7.749311 7.229925 8.170366 6.970226 8.430065 c
|
||||
6.710527 8.689764 6.289473 8.689764 6.029774 8.430065 c
|
||||
6.970226 7.489613 l
|
||||
h
|
||||
-0.470226 1.930065 m
|
||||
-0.729925 1.670366 -0.729925 1.249311 -0.470226 0.989613 c
|
||||
-0.210527 0.729914 0.210527 0.729914 0.470226 0.989613 c
|
||||
-0.470226 1.930065 l
|
||||
h
|
||||
6.029774 8.430065 m
|
||||
-0.470226 1.930065 l
|
||||
0.470226 0.989613 l
|
||||
6.970226 7.489613 l
|
||||
6.029774 8.430065 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 11.000000 4.540161 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
4.470226 4.989613 m
|
||||
4.729925 5.249311 4.729925 5.670366 4.470226 5.930065 c
|
||||
4.210527 6.189764 3.789473 6.189764 3.529774 5.930065 c
|
||||
4.470226 4.989613 l
|
||||
h
|
||||
-0.470226 1.930065 m
|
||||
-0.729925 1.670366 -0.729925 1.249311 -0.470226 0.989613 c
|
||||
-0.210527 0.729914 0.210527 0.729914 0.470226 0.989613 c
|
||||
-0.470226 1.930065 l
|
||||
h
|
||||
3.529774 5.930065 m
|
||||
-0.470226 1.930065 l
|
||||
0.470226 0.989613 l
|
||||
4.470226 4.989613 l
|
||||
3.529774 5.930065 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
3228
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 5 0 R
|
||||
/Contents 6 0 R
|
||||
/Parent 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<< /Kids [ 8 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<< /Pages 9 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000005203 00000 n
|
||||
0000005226 00000 n
|
||||
0000005707 00000 n
|
||||
0000005729 00000 n
|
||||
0000006027 00000 n
|
||||
0000009311 00000 n
|
||||
0000009334 00000 n
|
||||
0000009507 00000 n
|
||||
0000009581 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 10 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
9641
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Chart/Withdrawal.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chart/Withdrawal.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ton_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
117
submodules/TelegramUI/Images.xcassets/Chart/Withdrawal.imageset/ton_30.pdf
vendored
Normal file
117
submodules/TelegramUI/Images.xcassets/Chart/Withdrawal.imageset/ton_30.pdf
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 8.343262 7.454346 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.432826 10.484781 m
|
||||
1.133278 11.041084 1.536197 11.715655 2.168021 11.715654 c
|
||||
5.991703 11.715646 l
|
||||
5.991703 2.038759 l
|
||||
5.966153 2.072763 5.942646 2.109401 5.921499 2.148673 c
|
||||
1.432826 10.484781 l
|
||||
h
|
||||
7.321702 2.038787 m
|
||||
7.347245 2.072783 7.370744 2.109412 7.391885 2.148674 c
|
||||
11.880558 10.484761 l
|
||||
12.180106 11.041063 11.777191 11.715633 11.145367 11.715634 c
|
||||
7.321702 11.715643 l
|
||||
7.321702 2.038787 l
|
||||
h
|
||||
2.168024 13.045654 m
|
||||
0.529820 13.045658 -0.514872 11.296619 0.261800 9.854228 c
|
||||
4.750473 1.518121 l
|
||||
5.567911 0.000022 7.745473 0.000024 8.562911 1.518120 c
|
||||
13.051584 9.854208 l
|
||||
13.828257 11.296596 12.783570 13.045631 11.145370 13.045634 c
|
||||
2.168024 13.045654 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.000000 3.670044 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
19.334999 11.329956 m
|
||||
19.334999 6.174378 15.155578 1.994957 10.000000 1.994957 c
|
||||
10.000000 0.664955 l
|
||||
15.890117 0.664955 20.665001 5.439839 20.665001 11.329956 c
|
||||
19.334999 11.329956 l
|
||||
h
|
||||
10.000000 1.994957 m
|
||||
4.844422 1.994957 0.665000 6.174378 0.665000 11.329956 c
|
||||
-0.665000 11.329956 l
|
||||
-0.665000 5.439839 4.109883 0.664955 10.000000 0.664955 c
|
||||
10.000000 1.994957 l
|
||||
h
|
||||
0.665000 11.329956 m
|
||||
0.665000 16.485535 4.844422 20.664955 10.000000 20.664955 c
|
||||
10.000000 21.994957 l
|
||||
4.109883 21.994957 -0.665000 17.220074 -0.665000 11.329956 c
|
||||
0.665000 11.329956 l
|
||||
h
|
||||
10.000000 20.664955 m
|
||||
15.155578 20.664955 19.334999 16.485535 19.334999 11.329956 c
|
||||
20.665001 11.329956 l
|
||||
20.665001 17.220074 15.890117 21.994957 10.000000 21.994957 c
|
||||
10.000000 20.664955 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1634
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001724 00000 n
|
||||
0000001747 00000 n
|
||||
0000001920 00000 n
|
||||
0000001994 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2053
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/NoAds.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/NoAds.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "noads_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
111
submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/NoAds.imageset/noads_30.pdf
vendored
Normal file
111
submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/NoAds.imageset/noads_30.pdf
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.334961 3.604797 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
19.329964 20.216686 m
|
||||
19.329964 22.046368 17.201042 23.051056 15.788654 21.887915 c
|
||||
9.926381 17.060162 l
|
||||
5.940495 17.060162 l
|
||||
1.135226 21.865429 l
|
||||
0.875527 22.125128 0.454473 22.125128 0.194774 21.865429 c
|
||||
-0.064925 21.605730 -0.064925 21.184675 0.194774 20.924976 c
|
||||
20.194775 0.924976 l
|
||||
20.454472 0.665277 20.875526 0.665277 21.135227 0.924976 c
|
||||
21.394924 1.184675 21.394924 1.605730 21.135227 1.865429 c
|
||||
19.194319 3.806337 l
|
||||
19.281490 4.040243 19.329964 4.297546 19.329964 4.573635 c
|
||||
19.329964 20.216686 l
|
||||
h
|
||||
17.999962 5.000694 m
|
||||
17.999962 20.216686 l
|
||||
17.999962 20.922359 17.178879 21.309849 16.634146 20.861248 c
|
||||
10.623723 15.911488 l
|
||||
10.481375 15.794260 10.302706 15.730161 10.118303 15.730161 c
|
||||
7.270494 15.730161 l
|
||||
17.999962 5.000694 l
|
||||
h
|
||||
0.999961 12.395161 m
|
||||
0.999961 14.083057 1.896390 15.561582 3.239132 16.380619 c
|
||||
4.218666 15.401085 l
|
||||
3.101114 14.862392 2.329961 13.718833 2.329961 12.395161 c
|
||||
2.329961 10.553291 3.823091 9.060161 5.664961 9.060161 c
|
||||
10.118303 9.060161 l
|
||||
10.302710 9.060161 10.481379 8.996058 10.623723 8.878834 c
|
||||
11.287819 8.331931 l
|
||||
17.214378 2.405373 l
|
||||
16.726585 2.391582 16.223637 2.544184 15.788653 2.902407 c
|
||||
10.329961 7.397801 l
|
||||
10.329961 4.895161 l
|
||||
10.329961 3.423321 9.136800 2.230160 7.664961 2.230160 c
|
||||
6.193122 2.230160 4.999961 3.423321 4.999961 4.895161 c
|
||||
4.999961 7.777199 l
|
||||
2.738543 8.099955 0.999961 10.044524 0.999961 12.395161 c
|
||||
h
|
||||
8.999961 7.730161 m
|
||||
6.329961 7.730161 l
|
||||
6.329961 4.895161 l
|
||||
6.329961 4.157860 6.927661 3.560162 7.664961 3.560162 c
|
||||
8.402261 3.560162 8.999961 4.157860 8.999961 4.895161 c
|
||||
8.999961 7.730161 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1696
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001786 00000 n
|
||||
0000001809 00000 n
|
||||
0000001982 00000 n
|
||||
0000002056 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2115
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/Business/Intro.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Business/Intro.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "intro_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
226
submodules/TelegramUI/Images.xcassets/Premium/Business/Intro.imageset/intro_30.pdf
vendored
Normal file
226
submodules/TelegramUI/Images.xcassets/Premium/Business/Intro.imageset/intro_30.pdf
vendored
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.000000 3.620605 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
7.545889 3.473349 m
|
||||
7.397222 2.825180 l
|
||||
7.397222 2.825180 l
|
||||
7.545889 3.473349 l
|
||||
h
|
||||
5.266314 1.841906 m
|
||||
4.941034 2.421923 l
|
||||
4.941034 2.421923 l
|
||||
5.266314 1.841906 l
|
||||
h
|
||||
2.471971 1.400564 m
|
||||
2.214746 0.787325 l
|
||||
2.471971 1.400564 l
|
||||
h
|
||||
3.613619 5.411451 m
|
||||
3.220785 4.874882 l
|
||||
3.613619 5.411451 l
|
||||
h
|
||||
10.000000 20.714394 m
|
||||
15.216641 20.714394 19.334999 16.883762 19.334999 12.288486 c
|
||||
20.665001 12.288486 l
|
||||
20.665001 17.734751 15.829054 22.044395 10.000000 22.044395 c
|
||||
10.000000 20.714394 l
|
||||
h
|
||||
19.334999 12.288486 m
|
||||
19.334999 7.693209 15.216641 3.862577 10.000000 3.862577 c
|
||||
10.000000 2.532578 l
|
||||
15.829054 2.532578 20.665001 6.842221 20.665001 12.288486 c
|
||||
19.334999 12.288486 l
|
||||
h
|
||||
10.000000 3.862577 m
|
||||
9.203434 3.862577 8.431143 3.952570 7.694557 4.121517 c
|
||||
7.397222 2.825180 l
|
||||
8.231061 2.633926 9.103088 2.532578 10.000000 2.532578 c
|
||||
10.000000 3.862577 l
|
||||
h
|
||||
7.694557 4.121517 m
|
||||
7.296880 4.212730 6.988395 3.993872 6.866288 3.902136 c
|
||||
6.721982 3.793720 6.552248 3.631365 6.405339 3.496567 c
|
||||
6.085002 3.202635 5.640136 2.813988 4.941034 2.421923 c
|
||||
5.591593 1.261890 l
|
||||
6.417546 1.725096 6.948387 2.189802 7.304533 2.516592 c
|
||||
7.495866 2.692154 7.589569 2.782000 7.665158 2.838789 c
|
||||
7.762947 2.912256 7.630817 2.771601 7.397222 2.825180 c
|
||||
7.694557 4.121517 l
|
||||
h
|
||||
4.941034 2.421923 m
|
||||
4.518973 2.185225 3.984706 2.063803 3.502597 2.019852 c
|
||||
3.266236 1.998304 3.056921 1.996721 2.899934 2.005190 c
|
||||
2.821182 2.009439 2.761494 2.015905 2.721901 2.022039 c
|
||||
2.669684 2.030128 2.683973 2.032770 2.729195 2.013802 c
|
||||
2.214746 0.787325 l
|
||||
2.322490 0.742132 2.437665 0.720207 2.518294 0.707716 c
|
||||
2.611548 0.693270 2.716669 0.683144 2.828285 0.677122 c
|
||||
3.052035 0.665051 3.325697 0.668209 3.623343 0.695343 c
|
||||
4.209247 0.748756 4.947107 0.900455 5.591593 1.261890 c
|
||||
4.941034 2.421923 l
|
||||
h
|
||||
2.729195 2.013802 m
|
||||
2.749930 2.005104 2.882758 1.946646 2.968039 1.777298 c
|
||||
3.061458 1.591789 3.022986 1.426357 3.001172 1.364456 c
|
||||
2.982594 1.311743 2.964163 1.295038 2.989081 1.328539 c
|
||||
3.009508 1.356001 3.043106 1.396257 3.097501 1.456873 c
|
||||
3.303000 1.685873 3.720195 2.103415 4.080953 2.656437 c
|
||||
2.967016 3.383102 l
|
||||
2.678986 2.941568 2.367350 2.634583 2.107628 2.345158 c
|
||||
2.045717 2.276167 1.979485 2.199697 1.921927 2.122316 c
|
||||
1.868860 2.050974 1.794289 1.941309 1.746787 1.806519 c
|
||||
1.696047 1.662540 1.655866 1.425915 1.780159 1.179100 c
|
||||
1.896313 0.948444 2.092599 0.838560 2.214746 0.787325 c
|
||||
2.729195 2.013802 l
|
||||
h
|
||||
4.080953 2.656437 m
|
||||
4.494962 3.291088 4.613441 3.980347 4.585420 4.541803 c
|
||||
4.571406 4.822596 4.520174 5.086733 4.439862 5.311483 c
|
||||
4.369761 5.507658 4.241846 5.775683 4.006453 5.948020 c
|
||||
3.220785 4.874882 l
|
||||
3.130925 4.940670 3.145670 4.980782 3.187423 4.863937 c
|
||||
3.218965 4.775669 3.248780 4.641682 3.257073 4.475510 c
|
||||
3.273666 4.143038 3.201793 3.743004 2.967016 3.383102 c
|
||||
4.080953 2.656437 l
|
||||
h
|
||||
4.006453 5.948020 m
|
||||
1.882439 7.503058 0.665000 9.659539 0.665000 12.288486 c
|
||||
-0.665000 12.288486 l
|
||||
-0.665000 9.190428 0.792707 6.652533 3.220785 4.874882 c
|
||||
4.006453 5.948020 l
|
||||
h
|
||||
0.665000 12.288486 m
|
||||
0.665000 16.883762 4.783359 20.714394 10.000000 20.714394 c
|
||||
10.000000 22.044395 l
|
||||
4.170946 22.044395 -0.665000 17.734751 -0.665000 12.288486 c
|
||||
0.665000 12.288486 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 14.000000 9.669922 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 1.995078 m
|
||||
-0.367269 1.995078 -0.665000 1.697348 -0.665000 1.330078 c
|
||||
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
|
||||
0.000000 1.995078 l
|
||||
h
|
||||
3.000000 0.665078 m
|
||||
3.367269 0.665078 3.665000 0.962809 3.665000 1.330078 c
|
||||
3.665000 1.697348 3.367269 1.995078 3.000000 1.995078 c
|
||||
3.000000 0.665078 l
|
||||
h
|
||||
0.000000 0.665078 m
|
||||
3.000000 0.665078 l
|
||||
3.000000 1.995078 l
|
||||
0.000000 1.995078 l
|
||||
0.000000 0.665078 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 13.500000 9.669922 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.335000 1.330078 m
|
||||
1.335000 0.962809 1.632731 0.665078 2.000000 0.665078 c
|
||||
2.367269 0.665078 2.665000 0.962809 2.665000 1.330078 c
|
||||
1.335000 1.330078 l
|
||||
h
|
||||
2.000000 7.830078 m
|
||||
2.665000 7.830078 l
|
||||
2.665000 8.197348 2.367269 8.495078 2.000000 8.495078 c
|
||||
2.000000 7.830078 l
|
||||
h
|
||||
0.000000 8.495078 m
|
||||
-0.367269 8.495078 -0.665000 8.197348 -0.665000 7.830078 c
|
||||
-0.665000 7.462809 -0.367269 7.165078 0.000000 7.165078 c
|
||||
0.000000 8.495078 l
|
||||
h
|
||||
2.665000 1.330078 m
|
||||
2.665000 7.830078 l
|
||||
1.335000 7.830078 l
|
||||
1.335000 1.330078 l
|
||||
2.665000 1.330078 l
|
||||
h
|
||||
2.000000 8.495078 m
|
||||
0.000000 8.495078 l
|
||||
0.000000 7.165078 l
|
||||
2.000000 7.165078 l
|
||||
2.000000 8.495078 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 13.750000 19.750000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.250000 0.000000 m
|
||||
1.940356 0.000000 2.500000 0.559644 2.500000 1.250000 c
|
||||
2.500000 1.940356 1.940356 2.500000 1.250000 2.500000 c
|
||||
0.559644 2.500000 0.000000 1.940356 0.000000 1.250000 c
|
||||
0.000000 0.559644 0.559644 0.000000 1.250000 0.000000 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4852
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004942 00000 n
|
||||
0000004965 00000 n
|
||||
0000005138 00000 n
|
||||
0000005212 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
5271
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/BusinessPerk/Intro.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/BusinessPerk/Intro.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "introsetting_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
182
submodules/TelegramUI/Images.xcassets/Premium/BusinessPerk/Intro.imageset/introsetting_30.pdf
vendored
Normal file
182
submodules/TelegramUI/Images.xcassets/Premium/BusinessPerk/Intro.imageset/introsetting_30.pdf
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 2 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.000000 4.484375 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
10.000000 20.135742 m
|
||||
15.522848 20.135742 20.000000 16.065603 20.000000 11.044833 c
|
||||
20.000000 6.024063 15.522848 1.953924 10.000000 1.953924 c
|
||||
9.153261 1.953924 8.331102 2.049595 7.545889 2.229696 c
|
||||
7.397357 2.263765 7.228708 2.107983 6.947171 1.847927 c
|
||||
6.606689 1.533424 6.101101 1.066412 5.266314 0.598253 c
|
||||
4.199766 0.000120 2.722059 0.052011 2.471971 0.156912 c
|
||||
2.232201 0.257484 2.416753 0.457399 2.741760 0.809465 c
|
||||
2.966608 1.053034 3.258681 1.369423 3.523984 1.776117 c
|
||||
4.172771 2.770672 3.904685 3.954702 3.613619 4.167799 c
|
||||
1.337573 5.834144 0.000000 8.181331 0.000000 11.044833 c
|
||||
0.000000 16.065603 4.477152 20.135742 10.000000 20.135742 c
|
||||
h
|
||||
9.999961 15.265625 m
|
||||
10.690317 15.265625 11.249961 15.825269 11.249961 16.515625 c
|
||||
11.249961 17.205980 10.690317 17.765625 9.999961 17.765625 c
|
||||
9.309605 17.765625 8.749961 17.205980 8.749961 16.515625 c
|
||||
8.749961 15.825269 9.309605 15.265625 9.999961 15.265625 c
|
||||
h
|
||||
8.499961 13.680625 m
|
||||
8.132691 13.680625 7.834961 13.382895 7.834961 13.015625 c
|
||||
7.834961 12.648355 8.132691 12.350625 8.499961 12.350625 c
|
||||
9.834961 12.350625 l
|
||||
9.834961 7.180625 l
|
||||
8.999961 7.180625 l
|
||||
8.632691 7.180625 8.334961 6.882895 8.334961 6.515625 c
|
||||
8.334961 6.148355 8.632691 5.850625 8.999961 5.850625 c
|
||||
10.499961 5.850625 l
|
||||
11.999961 5.850625 l
|
||||
12.367230 5.850625 12.664961 6.148355 12.664961 6.515625 c
|
||||
12.664961 6.882895 12.367230 7.180625 11.999961 7.180625 c
|
||||
11.164961 7.180625 l
|
||||
11.164961 13.015625 l
|
||||
11.164961 13.382895 10.867230 13.680625 10.499961 13.680625 c
|
||||
8.499961 13.680625 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
1644
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 4 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
944
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /XObject << /X1 1 0 R >>
|
||||
/ExtGState << /E1 << /SMask << /Type /Mask
|
||||
/G 3 0 R
|
||||
/S /Alpha
|
||||
>>
|
||||
/Type /ExtGState
|
||||
>> >>
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Length 7 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
/X1 Do
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
46
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 5 0 R
|
||||
/Contents 6 0 R
|
||||
/Parent 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<< /Kids [ 8 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<< /Pages 9 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000001902 00000 n
|
||||
0000001925 00000 n
|
||||
0000003117 00000 n
|
||||
0000003139 00000 n
|
||||
0000003437 00000 n
|
||||
0000003539 00000 n
|
||||
0000003560 00000 n
|
||||
0000003733 00000 n
|
||||
0000003807 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 10 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
3867
|
||||
%%EOF
|
||||
|
|
@ -14818,25 +14818,39 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
let _ = (ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: threadId, { currentState in
|
||||
return currentState.withUpdatedComposeInputState(textInputState)
|
||||
})
|
||||
|> deliverOnMainQueue).startStandalone(completed: {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
|
||||
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
|
||||
|
||||
if let navigationController = strongSelf.effectiveNavigationController {
|
||||
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id: peerId))
|
||||
if let navigationController = strongSelf.effectiveNavigationController {
|
||||
let chatController: Signal<ChatController, NoError>
|
||||
if let threadId {
|
||||
chatController = chatControllerForForumThreadImpl(context: strongSelf.context, peerId: peerId, threadId: threadId)
|
||||
} else {
|
||||
chatController = .single(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id: peerId)))
|
||||
}
|
||||
|
||||
let _ = (chatController
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak navigationController] chatController in
|
||||
guard let strongSelf = self, let navigationController else {
|
||||
return
|
||||
}
|
||||
var viewControllers = navigationController.viewControllers
|
||||
let lastController = viewControllers.last as! ViewController
|
||||
viewControllers.remove(at: viewControllers.count - 2)
|
||||
viewControllers.insert(chatController, at: viewControllers.count - 1)
|
||||
lastController.navigationPresentation = .modal
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
strongSelf.controllerNavigationDisposable.set((chatController.ready.get()
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStrict(next: { _ in
|
||||
if let strongController = controller {
|
||||
strongController.dismiss()
|
||||
}
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak lastController] _ in
|
||||
lastController?.dismiss()
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1725,7 +1725,7 @@ extension ChatControllerImpl {
|
|||
}
|
||||
let editorController = MediaEditorScreen(
|
||||
context: self.context,
|
||||
mode: .stickerEditor,
|
||||
mode: .stickerEditor(mode: .generic),
|
||||
subject: .single(.asset(asset)),
|
||||
transitionIn: .gallery(
|
||||
MediaEditorScreen.TransitionIn.GalleryTransitionIn(
|
||||
|
|
@ -1756,8 +1756,6 @@ extension ChatControllerImpl {
|
|||
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
||||
)
|
||||
self.push(editorController)
|
||||
|
||||
|
||||
},
|
||||
dismissed: {}
|
||||
)
|
||||
|
|
@ -1768,68 +1766,4 @@ extension ChatControllerImpl {
|
|||
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.push(mainController)
|
||||
}
|
||||
|
||||
// func openStickerEditor() {
|
||||
// let mainController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
|
||||
// return nil
|
||||
// })
|
||||
//// controller.forceSourceRect = true
|
||||
//// controller.getSourceRect = getSourceRect
|
||||
// mainController.requestController = { [weak self, weak mainController] _, present in
|
||||
// guard let self else {
|
||||
// return
|
||||
// }
|
||||
// let mediaPickerController = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, subject: .assets(nil, .createSticker))
|
||||
// mediaPickerController.customSelection = { [weak self, weak mainController] controller, result in
|
||||
// guard let self else {
|
||||
// return
|
||||
// }
|
||||
// if let result = result as? PHAsset {
|
||||
// controller.updateHiddenMediaId(result.localIdentifier)
|
||||
// if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
|
||||
// let editorController = MediaEditorScreen(
|
||||
// context: self.context,
|
||||
// mode: .stickerEditor,
|
||||
// subject: .single(.asset(result)),
|
||||
// transitionIn: .gallery(
|
||||
// MediaEditorScreen.TransitionIn.GalleryTransitionIn(
|
||||
// sourceView: transitionView,
|
||||
// sourceRect: transitionView.bounds,
|
||||
// sourceImage: controller.transitionImage(for: result.localIdentifier)
|
||||
// )
|
||||
// ),
|
||||
// transitionOut: { finished, isNew in
|
||||
// if !finished {
|
||||
// return MediaEditorScreen.TransitionOut(
|
||||
// destinationView: transitionView,
|
||||
// destinationRect: transitionView.bounds,
|
||||
// destinationCornerRadius: 0.0
|
||||
// )
|
||||
// }
|
||||
// return nil
|
||||
// }, completion: { [weak self, weak mainController] result, commit in
|
||||
// mainController?.dismiss()
|
||||
// self?.chatDisplayNode.dismissInput()
|
||||
//
|
||||
// Queue.mainQueue().after(0.1) {
|
||||
// commit({})
|
||||
// if case let .sticker(file) = result.media {
|
||||
// self?.enqueueStickerFile(file)
|
||||
// }
|
||||
// }
|
||||
// } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
||||
// )
|
||||
// editorController.dismissed = { [weak controller] in
|
||||
// controller?.updateHiddenMediaId(nil)
|
||||
// }
|
||||
// self.push(editorController)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// present(mediaPickerController, mediaPickerController.mediaPickerContext)
|
||||
// }
|
||||
// mainController.navigationPresentation = .flatModal
|
||||
// mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
// self.push(mainController)
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ import BusinessLocationSetupScreen
|
|||
import BusinessHoursSetupScreen
|
||||
import AutomaticBusinessMessageSetupScreen
|
||||
import CollectibleItemInfoScreen
|
||||
import StickerPickerScreen
|
||||
import MediaEditor
|
||||
import MediaEditorScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
|
|
@ -2264,7 +2267,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return controller
|
||||
}
|
||||
|
||||
public func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController {
|
||||
public func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
|
|
@ -2272,7 +2275,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
let controller = PremiumBoostLevelsScreen(
|
||||
context: context,
|
||||
peerId: peerId,
|
||||
mode: .owner(subject: .stories),
|
||||
mode: .owner(subject: subject),
|
||||
status: boostStatus,
|
||||
myBoostStatus: myBoostStatus,
|
||||
openStats: openStats,
|
||||
|
|
@ -2306,9 +2309,47 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, isEditing: isEditing, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
|
||||
}
|
||||
|
||||
// func makeStickerEditorScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, initialSticker: TelegramMediaFile?, targetStickerPack: StickerPackReference?) -> ViewController {
|
||||
//
|
||||
// }
|
||||
public func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController {
|
||||
let subject: MediaEditorScreen.Subject
|
||||
let mode: MediaEditorScreen.Mode.StickerEditorMode
|
||||
if let file = source as? TelegramMediaFile {
|
||||
subject = .sticker(file)
|
||||
mode = .editing
|
||||
} else if let asset = source as? PHAsset {
|
||||
subject = .asset(asset)
|
||||
mode = .addingToPack
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
let controller = MediaEditorScreen(
|
||||
context: context,
|
||||
mode: .stickerEditor(mode: mode),
|
||||
subject: .single(subject),
|
||||
transitionIn: transitionArguments.flatMap { .gallery(
|
||||
MediaEditorScreen.TransitionIn.GalleryTransitionIn(
|
||||
sourceView: $0.0,
|
||||
sourceRect: $0.1,
|
||||
sourceImage: $0.2
|
||||
)
|
||||
) },
|
||||
transitionOut: { finished, isNew in
|
||||
if !finished, let transitionArguments {
|
||||
return MediaEditorScreen.TransitionOut(
|
||||
destinationView: transitionArguments.0,
|
||||
destinationRect: transitionArguments.0.bounds,
|
||||
destinationCornerRadius: 0.0
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}, completion: { result, commit in
|
||||
commit({})
|
||||
if case let .sticker(file) = result.media {
|
||||
completion(file)
|
||||
}
|
||||
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
||||
)
|
||||
return controller
|
||||
}
|
||||
|
||||
public func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController {
|
||||
return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion)
|
||||
|
|
@ -2321,6 +2362,17 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
public func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
|
||||
return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed)
|
||||
}
|
||||
|
||||
public func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController {
|
||||
let controller = StickerPickerScreen(context: context, inputData: inputData.get(), expanded: true, hasGifs: false, hasInteractiveStickers: false)
|
||||
controller.completion = { content in
|
||||
if let content, case let .file(file, _) = content {
|
||||
completion(file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController {
|
||||
return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ UIView * _Nullable getPortalViewSourceView(UIView * _Nonnull portalView);
|
|||
|
||||
NSObject * _Nullable makeBlurFilter();
|
||||
NSObject * _Nullable makeLuminanceToAlphaFilter();
|
||||
NSObject * _Nullable makeMonochromeFilter();
|
||||
|
||||
void setLayerDisableScreenshots(CALayer * _Nonnull layer, bool disableScreenshots);
|
||||
void setLayerContentsMaskMode(CALayer * _Nonnull layer, bool maskMode);
|
||||
|
|
|
|||
|
|
@ -232,6 +232,11 @@ NSObject * _Nullable makeLuminanceToAlphaFilter() {
|
|||
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"luminanceToAlpha"];
|
||||
}
|
||||
|
||||
NSObject * _Nullable makeMonochromeFilter() {
|
||||
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"colorMonochrome"];
|
||||
}
|
||||
|
||||
|
||||
void setLayerDisableScreenshots(CALayer * _Nonnull layer, bool disableScreenshots) {
|
||||
static UITextField *textField = nil;
|
||||
static UIView *secureView = nil;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue