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
8be8491b84
commit
142d59cf8f
55 changed files with 1851 additions and 1293 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -15669,3 +15669,7 @@ Error: %8$@";
|
|||
|
||||
"Conversation.Summary.Limit.Title" = "AI Summary";
|
||||
"Conversation.Summary.Limit.Text" = "Summarize large messages with AI – unlimited with Telegram Premium.";
|
||||
|
||||
"Appearance.SendWithCmdEnter" = "Send Messages with ⌘+Enter";
|
||||
|
||||
"Notification.StarsGift.Crafted" = "You crafted a new unique collectible";
|
||||
|
|
|
|||
|
|
@ -1294,6 +1294,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
var currentMediaInputSettings: Atomic<MediaInputSettings> { get }
|
||||
var currentStickerSettings: Atomic<StickerSettings> { get }
|
||||
var currentMediaDisplaySettings: Atomic<MediaDisplaySettings> { get }
|
||||
var currentChatSettings: Atomic<ChatSettings> { get }
|
||||
|
||||
var energyUsageSettings: EnergyUsageSettings { get }
|
||||
|
||||
|
|
|
|||
|
|
@ -1489,7 +1489,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
}
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendButton.alpha.isZero {
|
||||
self.sendButtonPressed()
|
||||
}
|
||||
|
|
@ -1497,7 +1497,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
}
|
||||
|
||||
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
return self.chatInputTextNodeShouldReturn()
|
||||
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
|
||||
}
|
||||
|
||||
private func applyUpdateSendButtonIcon() {
|
||||
|
|
|
|||
|
|
@ -530,6 +530,11 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
if let view = leftItemView.view {
|
||||
if view.superview == nil {
|
||||
self.navigationBarContainer.addSubview(view)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
leftItemTransition.setFrame(view: view, frame: leftItemFrame)
|
||||
}
|
||||
|
|
@ -587,17 +592,19 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
}
|
||||
|
||||
if let bottomItem = component.bottomItem {
|
||||
var bottomItemTransition = transition
|
||||
let bottomItemView: ComponentView<Empty>
|
||||
if let current = self.bottomItemView {
|
||||
bottomItemView = current
|
||||
} else {
|
||||
bottomItemTransition = .immediate
|
||||
bottomItemView = ComponentView<Empty>()
|
||||
self.bottomItemView = bottomItemView
|
||||
}
|
||||
|
||||
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: sheetEnvironment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let bottomItemSize = bottomItemView.update(
|
||||
transition: transition,
|
||||
transition: bottomItemTransition,
|
||||
component: bottomItem,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerSize.width - bottomInsets.left - bottomInsets.right, height: 52.0)
|
||||
|
|
@ -606,12 +613,24 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
if let view = bottomItemView.view {
|
||||
if view.superview == nil {
|
||||
self.bottomContainer.addSubview(view)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: view, frame: bottomItemFrame)
|
||||
bottomItemTransition.setFrame(view: view, frame: bottomItemFrame)
|
||||
}
|
||||
} else if let bottomItemView = self.bottomItemView {
|
||||
self.bottomItemView = nil
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
if !transition.animation.isImmediate {
|
||||
bottomItemView.view?.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
bottomItemView.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: availableSize.height - edgeEffectHeight), size: CGSize(width: fillingSize, height: edgeEffectHeight))
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public class ItemListActionItem: ListViewItem, ItemListItem {
|
|||
|
||||
public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
|
@ -107,7 +108,10 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
public init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
|
@ -132,6 +136,20 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
|
|
@ -211,6 +229,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.highlightNode.backgroundColor = item.presentationData.theme.list.itemSearchHighlightColor
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
|
|
@ -273,6 +292,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.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 + bottomStripeOffset), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/Utils/DeviceModel",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|
|||
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in
|
||||
pushControllerImpl?(controller)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}, dismissInput: {}, contentContext: nil, progress: nil, completion: nil)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -623,15 +623,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
|
||||
let removeSessionImpl: (Int64, @escaping () -> Void) -> Void = { sessionId, completion in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateSessionText),
|
||||
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateSession, color: .destructive, action: {
|
||||
dismissAction()
|
||||
let controller = textAlertController(
|
||||
context: context,
|
||||
title: nil,
|
||||
text: presentationData.strings.AuthSessions_TerminateSessionText,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateSession, action: {
|
||||
completion()
|
||||
|
||||
updateState {
|
||||
|
|
@ -649,11 +646,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
}
|
||||
context.sharedContext.updateNotificationTokensRegistration()
|
||||
}))
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical
|
||||
)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}
|
||||
|
||||
let removeWebSessionImpl: (Int64) -> Void = { sessionId in
|
||||
|
|
@ -690,16 +688,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
removeSessionImpl(sessionId, {})
|
||||
}, terminateOtherSessions: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateOtherSessionsText),
|
||||
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateOtherSessions, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
let controller = textAlertController(
|
||||
context: context,
|
||||
title: nil,
|
||||
text: presentationData.strings.AuthSessions_TerminateOtherSessionsText,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateOtherSessions, action: {
|
||||
updateState {
|
||||
return $0.withUpdatedTerminatingOtherSessions(true)
|
||||
}
|
||||
|
|
@ -715,11 +709,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
}
|
||||
context.sharedContext.updateNotificationTokensRegistration()
|
||||
}))
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical
|
||||
)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}, openSession: { session in
|
||||
let controller = RecentSessionScreen(context: context, subject: .session(session), updateAcceptSecretChats: { value in
|
||||
updateSessionDisposable.set(activeSessionsContext.updateSessionAcceptsSecretChats(session, accepts: value).start())
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -30,6 +30,7 @@ import ContextUI
|
|||
import QuickReactionSetupController
|
||||
import AvatarEditorScreen
|
||||
import PeerSelectionScreen
|
||||
import DeviceModel
|
||||
|
||||
enum SettingsSearchableItemIcon {
|
||||
case profile
|
||||
|
|
@ -3895,7 +3896,7 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
|
|||
present(.push, themeSettingsController(context: context, focusOnItemTag: itemTag))
|
||||
}
|
||||
|
||||
return [
|
||||
var items: [SettingsSearchableItem] = [
|
||||
SettingsSearchableItem(
|
||||
id: "appearance",
|
||||
title: strings.Settings_Appearance,
|
||||
|
|
@ -4094,14 +4095,31 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
|
|||
),
|
||||
SettingsSearchableItem(
|
||||
id: "appearance/tap-for-next-media",
|
||||
title: strings.Appearance_ShowNextMediaOnTap,
|
||||
icon: icon,
|
||||
breadcrumbs: [strings.Settings_Appearance],
|
||||
isVisible: false,
|
||||
present: { context, _, present in
|
||||
presentAppearanceSettings(context, present, .tapForNextMedia)
|
||||
}
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
if DeviceModel.current.isIpad {
|
||||
items.append(
|
||||
SettingsSearchableItem(
|
||||
id: "appearance/send-with-cmd-enter",
|
||||
title: strings.Appearance_SendWithCmdEnter,
|
||||
icon: icon,
|
||||
breadcrumbs: [strings.Settings_Appearance],
|
||||
isVisible: false,
|
||||
present: { context, _, present in
|
||||
presentAppearanceSettings(context, present, .sendWithCmdEnter)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
private func languageSearchableItems(context: AccountContext, localizations: [LocalizationInfo]) -> [SettingsSearchableItem] {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,7 @@ import AppBundle
|
|||
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.setBlendMode(.normal)
|
||||
context.clear(bounds)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if selected {
|
||||
|
|
@ -113,8 +108,10 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||
|
||||
override init() {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.clipsToBounds = true
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.cornerRadius = 15.0
|
||||
|
||||
self.overlayNode = ASImageNode()
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
|
||||
|
|
@ -199,6 +196,7 @@ private let selectedTextFont = Font.medium(12.0)
|
|||
|
||||
class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
|
@ -219,6 +217,9 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
|
|
@ -234,6 +235,20 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.addSubnode(self.containerNode)
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ThemeSettingsAppIconItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
return { item, params, neighbors in
|
||||
let contentSize: CGSize
|
||||
|
|
@ -280,6 +295,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.highlightNode.backgroundColor = item.theme.list.itemSearchHighlightColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
|
|
@ -321,6 +337,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.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))
|
||||
|
|
|
|||
|
|
@ -38,13 +38,34 @@ private final class ThemeSettingsControllerArguments {
|
|||
let openBubbleSettings: () -> Void
|
||||
let openPowerSavingSettings: () -> Void
|
||||
let openStickersAndEmoji: () -> Void
|
||||
let toggleSendWithCmdEnter: (Bool) -> Void
|
||||
let toggleShowNextMediaOnTap: (Bool) -> Void
|
||||
let selectAppIcon: (PresentationAppIcon) -> Void
|
||||
let editTheme: (PresentationCloudTheme) -> Void
|
||||
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
|
||||
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, openNameColorSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
init(
|
||||
context: AccountContext,
|
||||
selectTheme: @escaping (PresentationThemeReference) -> Void,
|
||||
openThemeSettings: @escaping () -> Void,
|
||||
openWallpaperSettings: @escaping () -> Void,
|
||||
openNameColorSettings: @escaping () -> Void,
|
||||
selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void,
|
||||
openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void,
|
||||
toggleNightTheme: @escaping (Bool) -> Void,
|
||||
openAutoNightTheme: @escaping () -> Void,
|
||||
openTextSize: @escaping () -> Void,
|
||||
openBubbleSettings: @escaping () -> Void,
|
||||
openPowerSavingSettings: @escaping () -> Void,
|
||||
openStickersAndEmoji: @escaping () -> Void,
|
||||
toggleSendWithCmdEnter: @escaping (Bool) -> Void,
|
||||
toggleShowNextMediaOnTap: @escaping (Bool) -> Void,
|
||||
selectAppIcon: @escaping (PresentationAppIcon) -> Void,
|
||||
editTheme: @escaping (PresentationCloudTheme) -> Void,
|
||||
themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void,
|
||||
colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.selectTheme = selectTheme
|
||||
self.openThemeSettings = openThemeSettings
|
||||
|
|
@ -58,6 +79,7 @@ private final class ThemeSettingsControllerArguments {
|
|||
self.openBubbleSettings = openBubbleSettings
|
||||
self.openPowerSavingSettings = openPowerSavingSettings
|
||||
self.openStickersAndEmoji = openStickersAndEmoji
|
||||
self.toggleSendWithCmdEnter = toggleSendWithCmdEnter
|
||||
self.toggleShowNextMediaOnTap = toggleShowNextMediaOnTap
|
||||
self.selectAppIcon = selectAppIcon
|
||||
self.editTheme = editTheme
|
||||
|
|
@ -84,6 +106,7 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
|
|||
case powerSaving
|
||||
case stickersAndEmoji
|
||||
case animations
|
||||
case sendWithCmdEnter
|
||||
case tapForNextMedia
|
||||
case nightMode
|
||||
|
||||
|
|
@ -112,6 +135,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
case powerSaving
|
||||
case stickersAndEmoji
|
||||
case otherHeader(PresentationTheme, String)
|
||||
case sendWithCmdEnter(PresentationTheme, String, Bool)
|
||||
case showNextMediaOnTap(PresentationTheme, String, Bool)
|
||||
case showNextMediaOnTapInfo(PresentationTheme, String)
|
||||
|
||||
|
|
@ -127,7 +151,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
return ThemeSettingsControllerSection.icon.rawValue
|
||||
case .powerSaving, .stickersAndEmoji:
|
||||
return ThemeSettingsControllerSection.message.rawValue
|
||||
case .otherHeader, .showNextMediaOnTap, .showNextMediaOnTapInfo:
|
||||
case .otherHeader, .sendWithCmdEnter, .showNextMediaOnTap, .showNextMediaOnTapInfo:
|
||||
return ThemeSettingsControllerSection.other.rawValue
|
||||
}
|
||||
}
|
||||
|
|
@ -164,10 +188,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
return 13
|
||||
case .otherHeader:
|
||||
return 14
|
||||
case .showNextMediaOnTap:
|
||||
case .sendWithCmdEnter:
|
||||
return 15
|
||||
case .showNextMediaOnTapInfo:
|
||||
case .showNextMediaOnTap:
|
||||
return 16
|
||||
case .showNextMediaOnTapInfo:
|
||||
return 17
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +289,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sendWithCmdEnter(lhsTheme, lhsTitle, lhsValue):
|
||||
if case let .sendWithCmdEnter(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .showNextMediaOnTap(lhsTheme, lhsTitle, lhsValue):
|
||||
if case let .showNextMediaOnTap(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
||||
return true
|
||||
|
|
@ -351,6 +383,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
})
|
||||
case let .otherHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .sendWithCmdEnter(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleSendWithCmdEnter(value)
|
||||
}, tag: ThemeSettingsEntryTag.sendWithCmdEnter)
|
||||
case let .showNextMediaOnTap(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleShowNextMediaOnTap(value)
|
||||
|
|
@ -361,7 +397,21 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
}
|
||||
}
|
||||
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], accountPeer: EnginePeer?, nameColors: PeerNameColors) -> [ThemeSettingsControllerEntry] {
|
||||
private func themeSettingsControllerEntries(
|
||||
presentationData: PresentationData,
|
||||
presentationThemeSettings: PresentationThemeSettings,
|
||||
chatSettings: ChatSettings,
|
||||
mediaSettings: MediaDisplaySettings,
|
||||
themeReference: PresentationThemeReference,
|
||||
availableThemes: [PresentationThemeReference],
|
||||
availableAppIcons: [PresentationAppIcon],
|
||||
currentAppIconName: String?,
|
||||
isPremium: Bool,
|
||||
chatThemes: [PresentationThemeReference],
|
||||
animatedEmojiStickers: [String: [StickerPackItem]],
|
||||
accountPeer: EnginePeer?,
|
||||
nameColors: PeerNameColors
|
||||
) -> [ThemeSettingsControllerEntry] {
|
||||
var entries: [ThemeSettingsControllerEntry] = []
|
||||
|
||||
let strings = presentationData.strings
|
||||
|
|
@ -437,6 +487,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
|||
}
|
||||
|
||||
entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased()))
|
||||
entries.append(.sendWithCmdEnter(presentationData.theme, strings.Appearance_SendWithCmdEnter, chatSettings.sendWithCmdEnter))
|
||||
entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap))
|
||||
entries.append(.showNextMediaOnTapInfo(presentationData.theme, strings.Appearance_ShowNextMediaOnTapInfo))
|
||||
|
||||
|
|
@ -557,6 +608,10 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in
|
||||
}))
|
||||
})
|
||||
}, toggleSendWithCmdEnter: { value in
|
||||
let _ = updateChatSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedSendWithCmdEnter(value)
|
||||
}).start()
|
||||
}, toggleShowNextMediaOnTap: { value in
|
||||
let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedShowNextMediaOnTap(value)
|
||||
|
|
@ -1035,9 +1090,26 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
})
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)))
|
||||
let signal = combineLatest(
|
||||
queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
context.sharedContext.accountManager.sharedData(keys: [
|
||||
ApplicationSpecificSharedDataKeys.presentationThemeSettings,
|
||||
ApplicationSpecificSharedDataKeys.chatSettings,
|
||||
ApplicationSpecificSharedDataKeys.mediaDisplaySettings,
|
||||
SharedDataKeys.chatThemes
|
||||
]),
|
||||
cloudThemes.get(),
|
||||
availableAppIcons,
|
||||
currentAppIconName.get(),
|
||||
removedThemeIndexesPromise.get(),
|
||||
animatedEmojiStickers,
|
||||
context.account.postbox.peerView(id: context.account.peerId),
|
||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
)
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
|
||||
let chatSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) ?? ChatSettings.defaultSettings
|
||||
let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
|
||||
|
||||
let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
|
||||
|
|
@ -1077,7 +1149,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
chatThemes.insert(.builtin(.dayClassic), at: 0)
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, chatSettings: chatSettings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,9 +289,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.addSubnode(self.gridNode)
|
||||
|
||||
self.titleContainer.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.topContainerNode)
|
||||
self.addSubnode(self.titleContainer)
|
||||
|
||||
self.addSubnode(self.topContainerNode)
|
||||
self.addSubnode(self.bottomContainerNode)
|
||||
|
||||
self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
|
|
@ -654,6 +654,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.reorderingGestureRecognizer = reorderingGestureRecognizer
|
||||
self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer)
|
||||
|
||||
self.gridNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.gridNode.clipsToBounds = true
|
||||
self.gridNode.layer.cornerRadius = 40.0
|
||||
|
||||
self.topEdgeEffectView.clipsToBounds = true
|
||||
self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.topEdgeEffectView.layer.cornerRadius = 40.0
|
||||
self.topContainerNode.view.addSubview(self.topEdgeEffectView)
|
||||
self.bottomContainerNode.view.addSubview(self.bottomEdgeEffectView)
|
||||
}
|
||||
|
|
@ -1891,7 +1898,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
|
||||
transition.updateFrame(node: self.bottomContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: 90.0)))
|
||||
|
||||
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset))
|
||||
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top))
|
||||
|
||||
let itemsPerRow = 5
|
||||
let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
|
||||
|
|
@ -1923,12 +1930,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
|
||||
let initialRevealedRowCount: CGFloat = 4.5
|
||||
|
||||
let topInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
topInset = 0.0
|
||||
topInset = titleAreaInset
|
||||
} else {
|
||||
topInset = insets.top + max(0.0, layout.size.height - floor(initialRevealedRowCount * itemWidth) - insets.top - actionAreaHeight - titleAreaInset)
|
||||
}
|
||||
|
||||
let additionalGridBottomInset = max(0.0, gridFrame.size.height - actionAreaHeight - contentHeight)
|
||||
let gridInsets = UIEdgeInsets(top: topInset, left: gridLeftInset, bottom: actionAreaHeight + additionalGridBottomInset, right: layout.size.width - fillingWidth - gridLeftInset)
|
||||
|
||||
|
|
@ -2112,6 +2120,10 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
buttonView.frame = buttonFrame
|
||||
}
|
||||
|
||||
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 90.0))
|
||||
transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
|
||||
self.topEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: actionAreaHeight - 90.0), size: CGSize(width: layout.size.width, height: 90.0))
|
||||
transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
|
||||
self.bottomEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
|
||||
|
|
@ -2133,7 +2145,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
return
|
||||
}
|
||||
|
||||
let minBackgroundY = gridFrame.minY - titleAreaInset
|
||||
let minBackgroundY = gridFrame.minY
|
||||
let unclippedBackgroundY = gridFrame.minY - presentationLayout.contentOffset.y - titleAreaInset
|
||||
|
||||
let offsetFromInitialPosition = presentationLayout.contentOffset.y + gridInsets.top
|
||||
|
|
@ -2173,7 +2185,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
var titleContainerFrame: CGRect
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
backgroundFrame.origin.y = min(0.0, backgroundFrame.origin.y)
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0)), size: CGSize())
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0) + 10.0), size: CGSize())
|
||||
} else {
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((56.0) / 2.0) + 10.0), size: CGSize())
|
||||
}
|
||||
|
|
@ -3103,12 +3115,12 @@ private final class StickerPackContextReferenceContentSource: ContextReferenceCo
|
|||
|
||||
|
||||
private func generateShadowImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 220.0, height: 220.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.saveGState()
|
||||
context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 100.0, height: 100.0), cornerRadius: 40.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
|
|
@ -3117,7 +3129,7 @@ private func generateShadowImage() -> UIImage? {
|
|||
context.setBlendMode(.clear)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70)
|
||||
})?.stretchableImage(withLeftCapWidth: 110, topCapHeight: 110)
|
||||
}
|
||||
|
||||
private func generateArrowImage(color: UIColor) -> UIImage? {
|
||||
|
|
@ -3290,38 +3302,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer {
|
|||
}
|
||||
}
|
||||
|
||||
private func generateShadowImage(corners: CACornerMask, radius: CGFloat) -> UIImage? {
|
||||
return generateImage(CGSize(width: 120.0, height: 120), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
// context.saveGState()
|
||||
context.setShadow(offset: CGSize(), blur: 28.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
|
||||
|
||||
var rectCorners: UIRectCorner = []
|
||||
if corners.contains(.layerMinXMinYCorner) {
|
||||
rectCorners.insert(.topLeft)
|
||||
}
|
||||
if corners.contains(.layerMaxXMinYCorner) {
|
||||
rectCorners.insert(.topRight)
|
||||
}
|
||||
if corners.contains(.layerMinXMaxYCorner) {
|
||||
rectCorners.insert(.bottomLeft)
|
||||
}
|
||||
if corners.contains(.layerMaxXMaxYCorner) {
|
||||
rectCorners.insert(.bottomRight)
|
||||
}
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 30.0, y: 30.0, width: 60.0, height: 60.0), byRoundingCorners: rectCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
// context.restoreGState()
|
||||
|
||||
// context.setBlendMode(.clear)
|
||||
// context.addPath(path)
|
||||
// context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 60)
|
||||
}
|
||||
|
||||
private final class CopyView: UIView {
|
||||
let shadow: UIImageView
|
||||
var snapshotView: UIView?
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0, canCraftAt: canCraftAt))
|
||||
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0, canCraftAt: canCraftAt, isCrafted: (flags & (1 << 16)) != 0))
|
||||
case let .messageActionPaidMessagesRefunded(messageActionPaidMessagesRefundedData):
|
||||
let (count, stars) = (messageActionPaidMessagesRefundedData.count, messageActionPaidMessagesRefundedData.stars)
|
||||
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool, isAuctionAcquired: Bool, toPeerId: EnginePeer.Id?, number: Int32?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool, canCraftAt: Int32?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool, canCraftAt: Int32?, isCrafted: Bool)
|
||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
||||
case conferenceCall(ConferenceCall)
|
||||
|
|
@ -404,7 +404,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else if let stars = decoder.decodeOptionalInt64ForKey("resaleStars") {
|
||||
resaleAmount = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars)
|
||||
}
|
||||
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false), canCraftAt: decoder.decodeOptionalInt32ForKey("canCraftAt"))
|
||||
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false), canCraftAt: decoder.decodeOptionalInt32ForKey("canCraftAt"), isCrafted: decoder.decodeBoolForKey("isCrafted", orElse: false))
|
||||
case 46:
|
||||
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
|
||||
case 47:
|
||||
|
|
@ -803,7 +803,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "number")
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer, canCraftAt):
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer, canCraftAt, isCrafted):
|
||||
encoder.encodeInt32(45, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
encoder.encodeBool(isUpgrade, forKey: "isUpgrade")
|
||||
|
|
@ -864,6 +864,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "canCraftAt")
|
||||
}
|
||||
|
||||
encoder.encodeBool(isCrafted, forKey: "isCrafted")
|
||||
case let .paidMessagesRefunded(count, stars):
|
||||
encoder.encodeInt32(46, forKey: "_rawValue")
|
||||
encoder.encodeInt32(count, forKey: "count")
|
||||
|
|
@ -971,7 +973,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
peerIds.append(toPeerId)
|
||||
}
|
||||
return peerIds
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _, _):
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _, _, _):
|
||||
var peerIds: [PeerId] = []
|
||||
if let peerId {
|
||||
peerIds.append(peerId)
|
||||
|
|
|
|||
|
|
@ -1524,7 +1524,7 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift
|
|||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||
}
|
||||
var media = currentMessage.media
|
||||
if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt) = action.action, case let .unique(uniqueGift) = gift {
|
||||
if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt, isCrafted) = action.action, case let .unique(uniqueGift) = gift {
|
||||
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
|
||||
media = [
|
||||
TelegramMediaAction(
|
||||
|
|
@ -1546,7 +1546,8 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift
|
|||
dropOriginalDetailsStars: nil,
|
||||
assigned: assigned,
|
||||
fromOffer: fromOffer,
|
||||
canCraftAt: canCraftAt
|
||||
canCraftAt: canCraftAt,
|
||||
isCrafted: isCrafted
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
@ -1653,7 +1654,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
|
|||
let message = updateNewMessageData.message
|
||||
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
@ -3186,12 +3187,6 @@ private final class CraftGiftsContextImpl {
|
|||
return _internal_craftStarGift(account: self.account, references: references)
|
||||
}
|
||||
|
||||
func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||
self.gifts.insert(gift, at: 0)
|
||||
self.count = self.count + 1
|
||||
self.pushState()
|
||||
}
|
||||
|
||||
func removeGifts(references: [StarGiftReference]) {
|
||||
let referencesSet = Set(references)
|
||||
self.gifts.removeAll { gift in
|
||||
|
|
@ -3325,12 +3320,6 @@ public final class CraftGiftsContext {
|
|||
}
|
||||
}
|
||||
|
||||
public func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||
self.impl.with { impl in
|
||||
impl.addGift(gift: gift)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeGifts(references: [StarGiftReference]) {
|
||||
self.impl.with { impl in
|
||||
impl.removeGifts(references: references)
|
||||
|
|
@ -3395,7 +3384,7 @@ func _internal_craftStarGift(account: Account, references: [StarGiftReference])
|
|||
let message = updateNewMessageData.message
|
||||
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
|
|||
|
|
@ -1658,7 +1658,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
|
|||
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid:
|
||||
receiptMessageId = nil
|
||||
}
|
||||
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
|
||||
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
|
|||
|
|
@ -217,9 +217,10 @@ public final class InitialPresentationDataAndSettings {
|
|||
public let mediaInputSettings: MediaInputSettings
|
||||
public let mediaDisplaySettings: MediaDisplaySettings
|
||||
public let stickerSettings: StickerSettings
|
||||
public let chatSettings: ChatSettings
|
||||
public let experimentalUISettings: ExperimentalUISettings
|
||||
|
||||
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) {
|
||||
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, chatSettings: ChatSettings, experimentalUISettings: ExperimentalUISettings) {
|
||||
self.presentationData = presentationData
|
||||
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
||||
self.autodownloadSettings = autodownloadSettings
|
||||
|
|
@ -228,6 +229,7 @@ public final class InitialPresentationDataAndSettings {
|
|||
self.mediaInputSettings = mediaInputSettings
|
||||
self.mediaDisplaySettings = mediaDisplaySettings
|
||||
self.stickerSettings = stickerSettings
|
||||
self.chatSettings = chatSettings
|
||||
self.experimentalUISettings = experimentalUISettings
|
||||
}
|
||||
}
|
||||
|
|
@ -245,6 +247,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
var experimentalUISettings: PreferencesEntry?
|
||||
var contactSynchronizationSettings: PreferencesEntry?
|
||||
var stickerSettings: PreferencesEntry?
|
||||
var chatSettings: PreferencesEntry?
|
||||
|
||||
init(
|
||||
localizationSettings: PreferencesEntry?,
|
||||
|
|
@ -257,7 +260,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
mediaDisplaySettings: PreferencesEntry?,
|
||||
experimentalUISettings: PreferencesEntry?,
|
||||
contactSynchronizationSettings: PreferencesEntry?,
|
||||
stickerSettings: PreferencesEntry?
|
||||
stickerSettings: PreferencesEntry?,
|
||||
chatSettings: PreferencesEntry?
|
||||
) {
|
||||
self.localizationSettings = localizationSettings
|
||||
self.presentationThemeSettings = presentationThemeSettings
|
||||
|
|
@ -270,6 +274,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
self.experimentalUISettings = experimentalUISettings
|
||||
self.contactSynchronizationSettings = contactSynchronizationSettings
|
||||
self.stickerSettings = stickerSettings
|
||||
self.chatSettings = chatSettings
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +290,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
let experimentalUISettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings)
|
||||
let contactSynchronizationSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings)
|
||||
let stickerSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings)
|
||||
let chatSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.chatSettings)
|
||||
|
||||
return InternalData(
|
||||
localizationSettings: localizationSettings,
|
||||
|
|
@ -297,7 +303,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
mediaDisplaySettings: mediaDisplaySettings,
|
||||
experimentalUISettings: experimentalUISettings,
|
||||
contactSynchronizationSettings: contactSynchronizationSettings,
|
||||
stickerSettings: stickerSettings
|
||||
stickerSettings: stickerSettings,
|
||||
chatSettings: chatSettings
|
||||
)
|
||||
}
|
||||
|> deliverOn(Queue(name: "PresentationData-Load", qos: .userInteractive))
|
||||
|
|
@ -365,6 +372,13 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
stickerSettings = StickerSettings.defaultSettings
|
||||
}
|
||||
|
||||
let chatSettings: ChatSettings
|
||||
if let value = internalData.chatSettings?.get(ChatSettings.self) {
|
||||
chatSettings = value
|
||||
} else {
|
||||
chatSettings = ChatSettings.defaultSettings
|
||||
}
|
||||
|
||||
let experimentalUISettings: ExperimentalUISettings = internalData.experimentalUISettings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||
|
||||
let contactSettings: ContactSynchronizationSettings = internalData.contactSynchronizationSettings?.get(ContactSynchronizationSettings.self) ?? ContactSynchronizationSettings.defaultSettings
|
||||
|
|
@ -413,7 +427,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
|
||||
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(themeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(themeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: themeSettings.chatBubbleSettings.mergeBubbleCorners)
|
||||
|
||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, experimentalUISettings: experimentalUISettings)
|
||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, chatSettings: chatSettings, experimentalUISettings: experimentalUISettings)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1262,7 +1262,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, fromOffer, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, fromOffer, _, isCrafted):
|
||||
if case let .unique(gift) = gift {
|
||||
if !forAdditionalServiceMessage && !"".isEmpty {
|
||||
attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor)
|
||||
|
|
@ -1338,7 +1338,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_BoughtYou(giftTitle, starsString)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes])
|
||||
}
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_TransferYou, font: titleFont, textColor: primaryTextColor)
|
||||
if isCrafted {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_Crafted, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_TransferYou, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
}
|
||||
} else if let senderId, let peer = message.peers[senderId] {
|
||||
if let peerId, let targetPeer = message.peers[peerId] {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
private let context: AccountContext
|
||||
private let subject: MessageActionUrlAuthResult
|
||||
|
||||
var peer: EnginePeer?
|
||||
|
||||
fileprivate var inProgress = false
|
||||
var allowWrite = true
|
||||
weak var controller: ViewController?
|
||||
|
|
@ -58,8 +60,17 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
init(context: AccountContext, subject: MessageActionUrlAuthResult) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
self.peer = peer
|
||||
self.updated()
|
||||
})
|
||||
}
|
||||
|
||||
func displayPhoneNumberConfirmation(commit: @escaping (Bool) -> Void) {
|
||||
|
|
@ -99,13 +110,12 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let peerButton = Child(AvatarComponent.self)
|
||||
let avatar = Child(AvatarComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
||||
let clientSection = Child(ListSectionComponent.self)
|
||||
let optionsSection = Child(ListSectionComponent.self)
|
||||
|
||||
let cancelButton = Child(ButtonComponent.self)
|
||||
let doneButton = Child(ButtonComponent.self)
|
||||
|
||||
|
|
@ -147,6 +157,24 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
if let peer = state.peer {
|
||||
let peerButton = peerButton.update(
|
||||
component: AvatarComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
peer: peer
|
||||
),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(peerButton
|
||||
.position(CGPoint(x: context.availableSize.width - 16.0 - peerButton.size.width / 2.0, y: 16.0 + peerButton.size.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
var contentHeight: CGFloat = 32.0
|
||||
let avatar = avatar.update(
|
||||
|
|
@ -320,7 +348,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
contentHeight += clientSection.size.height
|
||||
|
||||
if flags.contains(.requestWriteAccess) {
|
||||
contentHeight += 22.0
|
||||
contentHeight += 38.0
|
||||
|
||||
var optionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
optionsSectionItems.append(AnyComponentWithIdentity(id: "allowWrite", component: AnyComponent(ListActionItemComponent(
|
||||
|
|
@ -441,10 +469,6 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
contentHeight += doneButton.size.height
|
||||
contentHeight += buttonInsets.bottom
|
||||
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
@property (nonatomic, copy) bool (^ _Nullable shouldPaste)();
|
||||
@property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable);
|
||||
@property (nonatomic, copy) ChatInputTextViewImplTargetForAction * _Nullable (^ _Nullable targetForAction)(SEL _Nullable);
|
||||
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)();
|
||||
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)(UIKeyModifierFlags);
|
||||
@property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)();
|
||||
@property (nonatomic, copy) void (^ _Nullable dropAutocorrectioniOS16)();
|
||||
|
||||
|
|
|
|||
|
|
@ -136,15 +136,22 @@
|
|||
}
|
||||
|
||||
- (NSArray *)keyCommands {
|
||||
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handlePlainReturn:)];
|
||||
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handleReturn:)];
|
||||
UIKeyCommand *cmdReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierCommand action:@selector(handleReturn:)];
|
||||
return @[
|
||||
plainReturn
|
||||
plainReturn,
|
||||
cmdReturn
|
||||
];
|
||||
}
|
||||
|
||||
- (void)handlePlainReturn:(id)__unused sender {
|
||||
- (void)handleReturn:(UIKeyCommand *)__unused sender {
|
||||
UIKeyModifierFlags modifierFlags = 0;
|
||||
if ([sender isKindOfClass:[UIKeyCommand class]]) {
|
||||
modifierFlags = sender.modifierFlags;
|
||||
}
|
||||
|
||||
if (_shouldReturn) {
|
||||
_shouldReturn();
|
||||
_shouldReturn(modifierFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import TextNodeWithEntities
|
|||
|
||||
public protocol ChatInputTextNodeDelegate: AnyObject {
|
||||
func chatInputTextNodeDidUpdateText()
|
||||
func chatInputTextNodeShouldReturn() -> Bool
|
||||
func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool
|
||||
func chatInputTextNodeDidChangeSelection(dueToEditing: Bool)
|
||||
func chatInputTextNodeDidBeginEditing()
|
||||
func chatInputTextNodeDidFinishEditing()
|
||||
|
|
@ -1180,11 +1180,11 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate,
|
|||
}
|
||||
return self.customDelegate?.chatInputTextNodeShouldPaste() ?? true
|
||||
}
|
||||
self.shouldReturn = { [weak self] in
|
||||
self.shouldReturn = { [weak self] modifierFlags in
|
||||
guard let self else {
|
||||
return true
|
||||
}
|
||||
return self.customDelegate?.chatInputTextNodeShouldReturn() ?? true
|
||||
return self.customDelegate?.chatInputTextNodeShouldReturn(modifierFlags: modifierFlags) ?? true
|
||||
}
|
||||
self.backspaceWhileEmpty = { [weak self] in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
switch action.action {
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
default:
|
||||
break
|
||||
|
|
@ -690,7 +690,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, fromOffer, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, fromOffer, _, _):
|
||||
if case let .unique(uniqueGift) = gift {
|
||||
isStarGift = true
|
||||
|
||||
|
|
|
|||
|
|
@ -4528,15 +4528,29 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
}
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
var shouldSendMessage = false
|
||||
if self.sendActionButtons.sendButton.supernode != nil && !self.sendActionButtons.sendButton.isHidden && !self.sendActionButtons.sendContainerNode.alpha.isZero {
|
||||
self.sendButtonPressed()
|
||||
if let context = self.context, context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
|
||||
if modifierFlags.contains(.command) {
|
||||
shouldSendMessage = true
|
||||
}
|
||||
} else {
|
||||
if modifierFlags.isEmpty {
|
||||
shouldSendMessage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
if shouldSendMessage {
|
||||
self.sendButtonPressed()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
return self.chatInputTextNodeShouldReturn()
|
||||
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
|
||||
}
|
||||
|
||||
private func applyUpdateSendButtonIcon() {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import UIKitRuntimeUtils
|
|||
public final class GiftCompositionComponent: Component {
|
||||
public class ExternalState {
|
||||
public fileprivate(set) var previewPatternColor: UIColor?
|
||||
public fileprivate(set) var backgroundColor: UIColor?
|
||||
public fileprivate(set) var previewModel: StarGift.UniqueGift.Attribute?
|
||||
public fileprivate(set) var previewBackdrop: StarGift.UniqueGift.Attribute?
|
||||
public fileprivate(set) var previewSymbol: StarGift.UniqueGift.Attribute?
|
||||
|
|
@ -766,6 +767,7 @@ public final class GiftCompositionComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
component.externalState?.backgroundColor = backgroundColor
|
||||
component.externalState?.previewPatternColor = secondBackgroundColor
|
||||
|
||||
var animateBackdropSwipe = false
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ final class CraftTableComponent: Component {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupFailAnimation() {
|
||||
func setupFailureAnimation() {
|
||||
guard !self.didSetupFinishAnimation else {
|
||||
return
|
||||
}
|
||||
|
|
@ -127,41 +127,29 @@ final class CraftTableComponent: Component {
|
|||
}
|
||||
for i in 0 ..< min(2, availableStickers.count) {
|
||||
if let sticker = availableStickers[i].view {
|
||||
self.animationView.setSticker(sticker, face: 3 - i, mirror: isUpsideDown)
|
||||
let face: Int
|
||||
if isUpsideDown {
|
||||
face = i + 1
|
||||
} else {
|
||||
face = 3 - i
|
||||
}
|
||||
self.animationView.setSticker(sticker, face: face, mirror: isUpsideDown)
|
||||
}
|
||||
}
|
||||
|
||||
self.state?.updated()
|
||||
self.flipFaces = isUpsideDown
|
||||
|
||||
Queue.mainQueue().after(0.3, {
|
||||
self.failWillFinish = true
|
||||
self.component?.willFinish(false)
|
||||
})
|
||||
|
||||
if let failOverlayView = self.failOverlay.view as? LottieComponent.View {
|
||||
failOverlayView.isHidden = false
|
||||
failOverlayView.onFrameUpdate = { [weak self] frameIndex in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if frameIndex >= 5 && !self.failDidStartCrossAnimation {
|
||||
self.failDidStartCrossAnimation = true
|
||||
self.craftFailPlayOnce.invoke(Void())
|
||||
}
|
||||
if frameIndex >= 65 && !self.failDidBringToFront {
|
||||
self.failDidBringToFront = true
|
||||
failOverlayView.superview?.bringSubviewToFront(failOverlayView)
|
||||
|
||||
self.animationView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
if frameIndex >= 75 && !self.failWillFinish {
|
||||
self.failWillFinish = true
|
||||
self.component?.willFinish(false)
|
||||
}
|
||||
if frameIndex >= 82 && !self.failDidFinish {
|
||||
self.failDidFinish = true
|
||||
|
||||
failOverlayView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.component?.finished(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.craftFailOverlayPlayOnce.invoke(Void())
|
||||
Queue.mainQueue().after(0.5, {
|
||||
self.failDidFinish = true
|
||||
self.component?.finished(nil)
|
||||
})
|
||||
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.4))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +159,8 @@ final class CraftTableComponent: Component {
|
|||
}
|
||||
self.didSetupFinishAnimation = true
|
||||
|
||||
self.animationView.isSuccess = true
|
||||
|
||||
self.animationView.onFinishApproach = { [weak self] isUpsideDown in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -270,10 +260,10 @@ final class CraftTableComponent: Component {
|
|||
if index == 0 {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "background", component: AnyComponent(
|
||||
RoundedRectangle(color: component.buttonColor, cornerRadius: 28.0)
|
||||
FilledRoundedRectangleComponent(color: component.buttonColor, cornerRadius: .value(28.0), smoothCorners: true)
|
||||
))
|
||||
)
|
||||
if !component.isCrafting {
|
||||
if !component.isCrafting || self.isFailed {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "glass", component: AnyComponent(
|
||||
GlassBackgroundComponent(size: CGSize(width: cubeSide, height: cubeSide), cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)))
|
||||
|
|
@ -282,11 +272,20 @@ final class CraftTableComponent: Component {
|
|||
}
|
||||
if self.isFailed {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "fail", component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "CraftFail"),
|
||||
size: CGSize(width: 96.0, height: 96.0),
|
||||
playOnce: self.craftFailPlayOnce
|
||||
AnyComponentWithIdentity(id: "faildial", component: AnyComponent(
|
||||
DialIndicatorComponent(
|
||||
content: AnyComponentWithIdentity(id: "gift", component: AnyComponent(BundleIconComponent(name: "Premium/GiftCrash", tintColor: .white))),
|
||||
backgroundColor: .white.withAlphaComponent(0.1),
|
||||
foregroundColor: .white,
|
||||
diameter: 84.0,
|
||||
contentSize: CGSize(width: 44.0, height: 44.0),
|
||||
lineWidth: 5.0,
|
||||
fontSize: 18.0,
|
||||
progress: 0.0,
|
||||
value: component.gifts.count,
|
||||
suffix: "",
|
||||
isVisible: true,
|
||||
isFlipped: self.flipFaces
|
||||
)
|
||||
))
|
||||
)
|
||||
|
|
@ -300,7 +299,9 @@ final class CraftTableComponent: Component {
|
|||
diameter: 84.0,
|
||||
lineWidth: 5.0,
|
||||
fontSize: 18.0,
|
||||
percentage: permilleValue / 10,
|
||||
progress: CGFloat(permilleValue / 10 / 100),
|
||||
value: permilleValue / 10,
|
||||
suffix: "%",
|
||||
isVisible: !component.isCrafting
|
||||
)
|
||||
))
|
||||
|
|
@ -318,7 +319,7 @@ final class CraftTableComponent: Component {
|
|||
} else {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "background", component: AnyComponent(
|
||||
RoundedRectangle(color: component.buttonColor, cornerRadius: 28.0)
|
||||
FilledRoundedRectangleComponent(color: component.buttonColor, cornerRadius: .value(28.0), smoothCorners: true)
|
||||
))
|
||||
)
|
||||
faceItems.append(
|
||||
|
|
@ -412,35 +413,36 @@ final class CraftTableComponent: Component {
|
|||
self.setupSuccessAnimation(uniqueGift)
|
||||
}
|
||||
case .fail:
|
||||
self.setupFailAnimation()
|
||||
self.setupFailureAnimation()
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if self.isFailed {
|
||||
let failOverlaySize = self.failOverlay.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "CraftFailOverlay"),
|
||||
size: CGSize(width: availableSize.width, height: availableSize.width),
|
||||
playOnce: self.craftFailOverlayPlayOnce
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.width)
|
||||
)
|
||||
let failOverlayFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - failOverlaySize.width) / 2.0), y: floor((availableSize.height - failOverlaySize.height) / 2.0)), size: failOverlaySize)
|
||||
if let failOverlayView = self.failOverlay.view {
|
||||
if failOverlayView.superview == nil {
|
||||
failOverlayView.isHidden = true
|
||||
self.insertSubview(failOverlayView, belowSubview: self.animationView)
|
||||
}
|
||||
failOverlayView.frame = failOverlayFrame
|
||||
}
|
||||
}
|
||||
// if self.isFailed {
|
||||
// let failOverlaySize = self.failOverlay.update(
|
||||
// transition: .immediate,
|
||||
// component: AnyComponent(
|
||||
// LottieComponent(
|
||||
// content: LottieComponent.AppBundleContent(name: "CraftFailOverlay"),
|
||||
// size: CGSize(width: availableSize.width, height: availableSize.width),
|
||||
// playOnce: self.craftFailOverlayPlayOnce
|
||||
// )
|
||||
// ),
|
||||
// environment: {},
|
||||
// containerSize: CGSize(width: availableSize.width, height: availableSize.width)
|
||||
// )
|
||||
// let failOverlayFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - failOverlaySize.width) / 2.0), y: floor((availableSize.height - failOverlaySize.height) / 2.0)), size: failOverlaySize)
|
||||
// if let failOverlayView = self.failOverlay.view {
|
||||
// if failOverlayView.superview == nil {
|
||||
// failOverlayView.isHidden = true
|
||||
// self.insertSubview(failOverlayView, belowSubview: self.animationView)
|
||||
// }
|
||||
// failOverlayView.frame = failOverlayFrame
|
||||
// }
|
||||
// }
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
|
@ -546,7 +548,7 @@ final class GiftSlotComponent: Component {
|
|||
let backgroundFrame = CGRect(origin: .zero, size: availableSize).insetBy(dx: 1.0, dy: 1.0)
|
||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), transition: .immediate)
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
if component.gift == nil && component.isCrafting {
|
||||
if component.gift == nil && component.isCrafting && previousComponent?.isCrafting == false {
|
||||
transition.setBlur(layer: self.backgroundView.layer, radius: 10.0)
|
||||
self.backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false)
|
||||
transition.setBlur(layer: self.addIcon.layer, radius: 10.0)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ final class CubeAnimationView: UIView {
|
|||
private var finishDelayTimerY: Timer?
|
||||
private var cubeScale: Float = 1.0
|
||||
private var hasFiredFinishApproach = false
|
||||
|
||||
var isSuccess = false
|
||||
|
||||
var onFinishApproach: ((Bool) -> Void)?
|
||||
|
||||
|
|
@ -485,7 +487,7 @@ final class CubeAnimationView: UIView {
|
|||
let duration: TimeInterval = 0.2
|
||||
let startQuad = Quad(rect: sticker.frame)
|
||||
let animationView: UIView
|
||||
if let snapshot = sticker.snapshotView(afterScreenUpdates: true) {
|
||||
if let snapshot = sticker.snapshotView(afterScreenUpdates: false) {
|
||||
self.warpSnapshot?.removeFromSuperview()
|
||||
self.warpSnapshot = snapshot
|
||||
|
||||
|
|
@ -606,7 +608,7 @@ final class CubeAnimationView: UIView {
|
|||
}
|
||||
let stickerIndex = indices[index]
|
||||
if self.stickers.indices.contains(stickerIndex) {
|
||||
let isLast = stickerIndex == indices.count - 1
|
||||
let isLast = index == indices.count - 1
|
||||
self.launchStickerView(self.stickers[stickerIndex], emphasized: isLast, willFinish: isLast)
|
||||
}
|
||||
self.scheduleStickerSequence(from: index + 1, indices: indices)
|
||||
|
|
@ -697,10 +699,12 @@ final class CubeAnimationView: UIView {
|
|||
let upsideDown = abs(shortestAngleDelta(from: self.rotation.x, to: Float.pi)) < (Float.pi / 2)
|
||||
self.onFinishApproach?(upsideDown)
|
||||
}
|
||||
if absRemaining <= self.finishSuccessScaleTriggerAngle {
|
||||
if self.isSuccess, absRemaining <= self.finishSuccessScaleTriggerAngle {
|
||||
let raw = (self.finishSuccessScaleTriggerAngle - absRemaining) / self.finishSuccessScaleTriggerAngle
|
||||
let eased = raw * raw * (3 - 2 * raw)
|
||||
self.cubeScale = 1.0 + (self.finishSuccessScale - 1.0) * eased
|
||||
} else if !self.isSuccess {
|
||||
self.cubeScale = 1.0
|
||||
}
|
||||
if abs(remaining) < 0.0008 && abs(self.angularVelocity.y) < 0.0015 {
|
||||
self.finishRotationY = self.finishTargetYUnwrapped
|
||||
|
|
|
|||
|
|
@ -13,29 +13,41 @@ final class DialIndicatorComponent: Component {
|
|||
let backgroundColor: UIColor
|
||||
let foregroundColor: UIColor
|
||||
let diameter: CGFloat
|
||||
let contentSize: CGSize?
|
||||
let lineWidth: CGFloat
|
||||
let fontSize: CGFloat
|
||||
let percentage: Int
|
||||
let progress: CGFloat
|
||||
let value: Int
|
||||
let suffix: String
|
||||
let isVisible: Bool
|
||||
let isFlipped: Bool
|
||||
|
||||
public init(
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
backgroundColor: UIColor,
|
||||
foregroundColor: UIColor,
|
||||
diameter: CGFloat,
|
||||
contentSize: CGSize? = nil,
|
||||
lineWidth: CGFloat,
|
||||
fontSize: CGFloat,
|
||||
percentage: Int,
|
||||
isVisible: Bool = true
|
||||
progress: CGFloat,
|
||||
value: Int,
|
||||
suffix: String,
|
||||
isVisible: Bool = true,
|
||||
isFlipped: Bool = false,
|
||||
) {
|
||||
self.content = content
|
||||
self.backgroundColor = backgroundColor
|
||||
self.foregroundColor = foregroundColor
|
||||
self.diameter = diameter
|
||||
self.contentSize = contentSize
|
||||
self.lineWidth = lineWidth
|
||||
self.fontSize = fontSize
|
||||
self.percentage = percentage
|
||||
self.progress = progress
|
||||
self.value = value
|
||||
self.suffix = suffix
|
||||
self.isVisible = isVisible
|
||||
self.isFlipped = isFlipped
|
||||
}
|
||||
|
||||
public static func ==(lhs: DialIndicatorComponent, rhs: DialIndicatorComponent) -> Bool {
|
||||
|
|
@ -51,18 +63,30 @@ final class DialIndicatorComponent: Component {
|
|||
if lhs.diameter != rhs.diameter {
|
||||
return false
|
||||
}
|
||||
if lhs.contentSize != rhs.contentSize {
|
||||
return false
|
||||
}
|
||||
if lhs.lineWidth != rhs.lineWidth {
|
||||
return false
|
||||
}
|
||||
if lhs.fontSize != rhs.fontSize {
|
||||
return false
|
||||
}
|
||||
if lhs.percentage != rhs.percentage {
|
||||
if lhs.progress != rhs.progress {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
if lhs.suffix != rhs.suffix {
|
||||
return false
|
||||
}
|
||||
if lhs.isVisible != rhs.isVisible {
|
||||
return false
|
||||
}
|
||||
if lhs.isFlipped != rhs.isFlipped {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +143,7 @@ final class DialIndicatorComponent: Component {
|
|||
self.foregroundLayer.path = CGPath(ellipseIn: pathFrame, transform: nil)
|
||||
self.foregroundLayer.transform = CATransform3DMakeRotation(.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
self.foregroundLayer.strokeStart = strokeStart
|
||||
transition.setShapeLayerStrokeEnd(layer: self.foregroundLayer, strokeEnd: strokeStart + (strokeEnd - strokeStart) * (CGFloat(component.percentage) / 100.0))
|
||||
transition.setShapeLayerStrokeEnd(layer: self.foregroundLayer, strokeEnd: strokeStart + (strokeEnd - strokeStart) * component.progress)
|
||||
self.foregroundLayer.frame = CGRect(origin: .zero, size: pathSize)
|
||||
|
||||
if previousComponent?.content.id != component.content.id {
|
||||
|
|
@ -136,7 +160,8 @@ final class DialIndicatorComponent: Component {
|
|||
self.content = ComponentView()
|
||||
}
|
||||
|
||||
let contentFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: component.diameter - 16.0, height: component.diameter - 16.0))
|
||||
let contentSize = component.contentSize ?? CGSize(width: component.diameter - 16.0, height: component.diameter - 16.0)
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((pathSize.width - contentSize.width) / 2.0), y: floorToScreenPixels((pathSize.height - contentSize.height) / 2.0)), size: contentSize)
|
||||
let _ = self.content.update(
|
||||
transition: .immediate,
|
||||
component: component.content.component,
|
||||
|
|
@ -154,10 +179,12 @@ final class DialIndicatorComponent: Component {
|
|||
contentView.frame = contentFrame
|
||||
}
|
||||
|
||||
let labelItems: [AnimatedTextComponent.Item] = [
|
||||
AnimatedTextComponent.Item(id: "percent", content: .number(component.percentage, minDigits: 1)),
|
||||
AnimatedTextComponent.Item(id: "suffix", content: .text("%"))
|
||||
var labelItems: [AnimatedTextComponent.Item] = [
|
||||
AnimatedTextComponent.Item(id: "percent", content: .number(component.value, minDigits: 1))
|
||||
]
|
||||
if !component.suffix.isEmpty {
|
||||
labelItems.append(AnimatedTextComponent.Item(id: "suffix", content: .text(component.suffix)))
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: transition,
|
||||
|
|
@ -181,6 +208,8 @@ final class DialIndicatorComponent: Component {
|
|||
transition.setAlpha(view: self.containerView, alpha: component.isVisible ? 1.0 : 0.0)
|
||||
transition.setBlur(layer: self.containerView.layer, radius: component.isVisible ? 0.0 : 10.0)
|
||||
|
||||
self.containerView.transform = CGAffineTransform(rotationAngle: component.isFlipped ? .pi : 0.0)
|
||||
|
||||
self.containerView.frame = CGRect(origin: .zero, size: pathSize)
|
||||
|
||||
return pathSize
|
||||
|
|
|
|||
|
|
@ -42,27 +42,35 @@ private final class CraftGiftPageContent: Component {
|
|||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
class ExternalState {
|
||||
fileprivate(set) var giftMap: [Int64: GiftItem]
|
||||
fileprivate(set) var giftsMap: [Int64: GiftItem]
|
||||
fileprivate(set) var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
||||
fileprivate(set) var displayFailure = false
|
||||
|
||||
fileprivate(set) var testFailOrSuccess: Bool?
|
||||
|
||||
public init() {
|
||||
self.giftMap = [:]
|
||||
self.giftsMap = [:]
|
||||
}
|
||||
}
|
||||
|
||||
enum DisplayState {
|
||||
case `default`
|
||||
case crafting
|
||||
case failure
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let craftContext: CraftGiftsContext
|
||||
let resaleContext: () -> ResaleGiftsContext?
|
||||
let colors: (UIColor, UIColor, UIColor, UIColor, UIColor)
|
||||
let gift: StarGift.UniqueGift
|
||||
let selectedGiftIds: [Int32: Int64]
|
||||
let displayCraftInfo: Bool
|
||||
let isCrafting: Bool
|
||||
let inProgress: Bool
|
||||
let displayState: DisplayState
|
||||
let displayInfo: Bool
|
||||
let result: CraftTableComponent.Result?
|
||||
let screenSize: CGSize
|
||||
let externalState: ExternalState
|
||||
let starsTopUpOptionsPromise: Promise<[StarsTopUpOption]?>
|
||||
let selectGift: (Int32, GiftItem) -> Void
|
||||
let removeGift: (Int32) -> Void
|
||||
let dismiss: () -> Void
|
||||
|
|
@ -74,12 +82,12 @@ private final class CraftGiftPageContent: Component {
|
|||
colors: (UIColor, UIColor, UIColor, UIColor, UIColor),
|
||||
gift: StarGift.UniqueGift,
|
||||
selectedGiftIds: [Int32: Int64],
|
||||
displayCraftInfo: Bool,
|
||||
isCrafting: Bool,
|
||||
inProgress: Bool,
|
||||
displayState: DisplayState,
|
||||
displayInfo: Bool,
|
||||
result: CraftTableComponent.Result?,
|
||||
screenSize: CGSize,
|
||||
externalState: ExternalState,
|
||||
starsTopUpOptionsPromise: Promise<[StarsTopUpOption]?>,
|
||||
selectGift: @escaping (Int32, GiftItem) -> Void,
|
||||
removeGift: @escaping (Int32) -> Void,
|
||||
dismiss: @escaping () -> Void
|
||||
|
|
@ -90,12 +98,12 @@ private final class CraftGiftPageContent: Component {
|
|||
self.colors = colors
|
||||
self.gift = gift
|
||||
self.selectedGiftIds = selectedGiftIds
|
||||
self.displayCraftInfo = displayCraftInfo
|
||||
self.isCrafting = isCrafting
|
||||
self.inProgress = inProgress
|
||||
self.displayState = displayState
|
||||
self.displayInfo = displayInfo
|
||||
self.result = result
|
||||
self.screenSize = screenSize
|
||||
self.externalState = externalState
|
||||
self.starsTopUpOptionsPromise = starsTopUpOptionsPromise
|
||||
self.selectGift = selectGift
|
||||
self.removeGift = removeGift
|
||||
self.dismiss = dismiss
|
||||
|
|
@ -114,13 +122,10 @@ private final class CraftGiftPageContent: Component {
|
|||
if lhs.selectedGiftIds != rhs.selectedGiftIds {
|
||||
return false
|
||||
}
|
||||
if lhs.displayCraftInfo != rhs.displayCraftInfo {
|
||||
if lhs.displayState != rhs.displayState {
|
||||
return false
|
||||
}
|
||||
if lhs.isCrafting != rhs.isCrafting {
|
||||
return false
|
||||
}
|
||||
if lhs.inProgress != rhs.inProgress {
|
||||
if lhs.displayInfo != rhs.displayInfo {
|
||||
return false
|
||||
}
|
||||
if lhs.screenSize != rhs.screenSize {
|
||||
|
|
@ -138,19 +143,22 @@ private final class CraftGiftPageContent: Component {
|
|||
private let title = ComponentView<Empty>()
|
||||
private let descriptionText = ComponentView<Empty>()
|
||||
|
||||
private let craftingTitle = ComponentView<Empty>()
|
||||
private let craftingSubtitle = ComponentView<Empty>()
|
||||
private let craftingDescription = ComponentView<Empty>()
|
||||
private let craftingProbability = ComponentView<Empty>()
|
||||
private var craftingProbabilityMeasure = ComponentView<Empty>()
|
||||
private var craftTable = ComponentView<Empty>()
|
||||
|
||||
private var backdropDial = ComponentView<Empty>()
|
||||
private var symbolDial = ComponentView<Empty>()
|
||||
private var variantsButton = ComponentView<Empty>()
|
||||
private var variantsButtonMeasure = ComponentView<Empty>()
|
||||
|
||||
private var craftTable = ComponentView<Empty>()
|
||||
private var selectedGifts: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private let craftingTitle = ComponentView<Empty>()
|
||||
private let craftingSubtitle = ComponentView<Empty>()
|
||||
private let craftingDescription = ComponentView<Empty>()
|
||||
private let craftingProbability = ComponentView<Empty>()
|
||||
private var craftingProbabilityMeasure = ComponentView<Empty>()
|
||||
|
||||
private let failureTitle = ComponentView<Empty>()
|
||||
private let failureDescription = ComponentView<Empty>()
|
||||
private var failedGifts: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
private let infoContainer = UIView()
|
||||
private var infoBackground = SimpleLayer()
|
||||
|
|
@ -167,13 +175,10 @@ private final class CraftGiftPageContent: Component {
|
|||
private let upgradePreviewDisposable = DisposableSet()
|
||||
private var upgradePreview: [StarGift.UniqueGift.Attribute]?
|
||||
private var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
||||
|
||||
private let starsTopUpOptionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||
|
||||
|
||||
private var availableGifts: [GiftItem] = []
|
||||
private var giftMap: [Int64: GiftItem] = [:]
|
||||
private var isCrafting = false
|
||||
private var isFailing = false
|
||||
|
||||
private var component: CraftGiftPageContent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
|
@ -260,7 +265,7 @@ private final class CraftGiftPageContent: Component {
|
|||
initialGiftItem
|
||||
]
|
||||
self.giftMap = [initialGiftItem.gift.id: initialGiftItem]
|
||||
component.externalState.giftMap = self.giftMap
|
||||
component.externalState.giftsMap = self.giftMap
|
||||
|
||||
self.craftStateDisposable = (component.craftContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
|
|
@ -296,7 +301,7 @@ private final class CraftGiftPageContent: Component {
|
|||
}
|
||||
self.availableGifts = items
|
||||
self.giftMap = map
|
||||
self.component?.externalState.giftMap = self.giftMap
|
||||
self.component?.externalState.giftsMap = self.giftMap
|
||||
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
})
|
||||
|
|
@ -322,7 +327,7 @@ private final class CraftGiftPageContent: Component {
|
|||
|
||||
self.upgradePreviewDisposable.add((.single(nil) |> then(component.context.engine.payments.cachedStarGifts())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] starGifts in
|
||||
guard let self, let starGifts else {
|
||||
guard let self, let component = self.component, let starGifts else {
|
||||
return
|
||||
}
|
||||
var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
||||
|
|
@ -332,9 +337,8 @@ private final class CraftGiftPageContent: Component {
|
|||
}
|
||||
}
|
||||
self.starGiftsMap = starGiftsMap
|
||||
component.externalState.starGiftsMap = starGiftsMap
|
||||
}))
|
||||
|
||||
self.starsTopUpOptionsPromise.set(component.context.engine.payments.starsTopUpOptions() |> map(Optional.init))
|
||||
}
|
||||
|
||||
transition.setGradientColors(layer: self.background, colors: [component.colors.0, component.colors.1])
|
||||
|
|
@ -348,6 +352,8 @@ private final class CraftGiftPageContent: Component {
|
|||
self.state = state
|
||||
self.environment = environment
|
||||
|
||||
let isCrafting = [.crafting, .failure].contains(component.displayState)
|
||||
|
||||
var selectedGifts: [Int32: GiftItem] = [:]
|
||||
for (index, giftId) in component.selectedGiftIds {
|
||||
if let gift = self.giftMap[giftId] {
|
||||
|
|
@ -388,14 +394,14 @@ private final class CraftGiftPageContent: Component {
|
|||
avatarScale: 1.0,
|
||||
defaultHeight: 300.0,
|
||||
gradientOnTop: true,
|
||||
avatarTransitionFraction: self.isFailing ? 1.0 : 0.0,
|
||||
avatarTransitionFraction: 0.0,
|
||||
patternTransitionFraction: 0.0,
|
||||
patternIconScale: 1.5
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 169.0 * 2.0)
|
||||
)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: component.isCrafting ? floor((component.screenSize.height - backgroundSize.height) / 2.0) : 0.0), size: backgroundSize)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: isCrafting && !"".isEmpty ? floor((component.screenSize.height - backgroundSize.height) / 2.0) : 0.0), size: backgroundSize)
|
||||
if let backgroundView = self.pattern.view {
|
||||
if backgroundView.layer.superlayer == nil {
|
||||
backgroundTransition = .immediate
|
||||
|
|
@ -420,8 +426,8 @@ private final class CraftGiftPageContent: Component {
|
|||
self.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
transition.setAlpha(view: titleView, alpha: component.isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: titleView.layer, radius: component.isCrafting ? 10.0 : 0.0)
|
||||
transition.setAlpha(view: titleView, alpha: 1.0)
|
||||
//transition.setBlur(layer: titleView.layer, radius: component.isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
let giftTitle = "\(component.gift.title) #\(formatCollectibleNumber(component.gift.number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
|
|
@ -431,7 +437,7 @@ private final class CraftGiftPageContent: Component {
|
|||
let descriptionFont = Font.regular(13.0)
|
||||
let descriptionBoldFont = Font.semibold(13.0)
|
||||
let descriptionColor = UIColor.white
|
||||
let rawDescriptionString = "Add up to **4 gifts** to craft new\n**$ \(giftTitle)**.\n\nIf crafting fails, all selected gifts\nwill be consumed."
|
||||
let rawDescriptionString = "Add up to **4 gifts** to craft new\n**$ \(giftTitle)**.\n\nIf crafting fails, all used gifts\nwill be lost."
|
||||
let descriptionString = parseMarkdownIntoAttributedString(rawDescriptionString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionColor), linkAttribute: { _ in return nil })).mutableCopy() as! NSMutableAttributedString
|
||||
|
||||
if let gift = self.starGiftsMap[component.gift.giftId] {
|
||||
|
|
@ -565,7 +571,9 @@ private final class CraftGiftPageContent: Component {
|
|||
diameter: 48.0,
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
percentage: backdropPermille / 10
|
||||
progress: CGFloat(backdropPermille / 10 / 100),
|
||||
value: backdropPermille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
|
|
@ -609,9 +617,12 @@ private final class CraftGiftPageContent: Component {
|
|||
backgroundColor: .white.withAlphaComponent(0.1),
|
||||
foregroundColor: .white,
|
||||
diameter: 48.0,
|
||||
contentSize: CGSize(width: 28.0, height: 28.0),
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
percentage: symbolPermille / 10
|
||||
progress: CGFloat(symbolPermille / 10 / 100),
|
||||
value: symbolPermille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
|
|
@ -726,7 +737,7 @@ private final class CraftGiftPageContent: Component {
|
|||
craftContentHeight += 160.0
|
||||
|
||||
let originalCraftContentHeight = craftContentHeight
|
||||
if component.isCrafting {
|
||||
if !"".isEmpty, isCrafting {
|
||||
craftContentHeight = component.screenSize.height
|
||||
}
|
||||
|
||||
|
|
@ -736,8 +747,8 @@ private final class CraftGiftPageContent: Component {
|
|||
self.addSubview(descriptionTextView)
|
||||
}
|
||||
transition.setFrame(view: descriptionTextView, frame: descriptionTextFrame)
|
||||
transition.setAlpha(view: descriptionTextView, alpha: component.isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: descriptionTextView.layer, radius: component.isCrafting ? 10.0 : 0.0)
|
||||
transition.setAlpha(view: descriptionTextView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: descriptionTextView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
let backdropDialFrame = CGRect(origin: CGPoint(x: availableSize.width * 0.5 - 9.0 - backdropDialSize.width, y: craftContentHeight - 145.0 - 78.0), size: backdropDialSize)
|
||||
|
|
@ -746,8 +757,8 @@ private final class CraftGiftPageContent: Component {
|
|||
self.addSubview(backdropDialView)
|
||||
}
|
||||
transition.setFrame(view: backdropDialView, frame: backdropDialFrame)
|
||||
transition.setAlpha(view: backdropDialView, alpha: component.isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: backdropDialView.layer, radius: component.isCrafting ? 10.0 : 0.0)
|
||||
transition.setAlpha(view: backdropDialView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: backdropDialView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
let symbolDialFrame = CGRect(origin: CGPoint(x: availableSize.width * 0.5 + 9.0, y: craftContentHeight - 145.0 - 78.0), size: symbolDialSize)
|
||||
|
|
@ -756,14 +767,14 @@ private final class CraftGiftPageContent: Component {
|
|||
self.addSubview(symbolDialView)
|
||||
}
|
||||
transition.setFrame(view: symbolDialView, frame: symbolDialFrame)
|
||||
transition.setAlpha(view: symbolDialView, alpha: component.isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: symbolDialView.layer, radius: component.isCrafting ? 10.0 : 0.0)
|
||||
transition.setAlpha(view: symbolDialView, alpha: isCrafting ? 0.0 : 1.0)
|
||||
transition.setBlur(layer: symbolDialView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
}
|
||||
|
||||
let variantsButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - variantsButtonSize.width) / 2.0), y: craftContentHeight - 145.0), size: variantsButtonSize)
|
||||
var varitantsButtonTransition = transition
|
||||
if let variantsButtonView = self.variantsButton.view {
|
||||
if variantsButtonView.superview == nil && !component.isCrafting {
|
||||
if variantsButtonView.superview == nil && component.displayState == .default {
|
||||
varitantsButtonTransition = .immediate
|
||||
if let symbolDialView = self.symbolDial.view {
|
||||
self.insertSubview(variantsButtonView, aboveSubview: symbolDialView)
|
||||
|
|
@ -772,9 +783,9 @@ private final class CraftGiftPageContent: Component {
|
|||
}
|
||||
}
|
||||
varitantsButtonTransition.setFrame(view: variantsButtonView, frame: variantsButtonFrame)
|
||||
varitantsButtonTransition.setBlur(layer: variantsButtonView.layer, radius: component.isCrafting ? 10.0 : 0.0)
|
||||
if component.isCrafting {
|
||||
variantsButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.41, removeOnCompletion: false, completion: { _ in
|
||||
varitantsButtonTransition.setBlur(layer: variantsButtonView.layer, radius: isCrafting ? 10.0 : 0.0)
|
||||
if component.displayState == .crafting {
|
||||
variantsButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
variantsButtonView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
|
@ -782,11 +793,12 @@ private final class CraftGiftPageContent: Component {
|
|||
|
||||
|
||||
let permilleValue = selectedGifts.reduce(0, { $0 + Int($1.value.gift.craftChancePermille ?? 0) })
|
||||
if component.isCrafting {
|
||||
var craftingOriginY = craftContentHeight * 0.5 + 160.0
|
||||
if component.displayState == .crafting {
|
||||
//var craftingOriginY = craftContentHeight * 0.5 + 160.0
|
||||
var craftingOriginY = craftContentHeight * 0.5 - 16.0
|
||||
let offset = -(craftContentHeight - originalCraftContentHeight)
|
||||
|
||||
let titleSize = self.craftingTitle.update(
|
||||
let craftingTitleSize = self.craftingTitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Crafting", font: Font.bold(20.0), textColor: .white)))
|
||||
|
|
@ -794,21 +806,21 @@ private final class CraftGiftPageContent: Component {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) * 0.5), y: craftingOriginY), size: titleSize)
|
||||
if let titleView = self.craftingTitle.view {
|
||||
if titleView.superview == nil {
|
||||
transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
|
||||
transition.animateBlur(layer: titleView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
transition.animatePosition(view: titleView, from: CGPoint(x: 0.0, y: offset), to: .zero, additive: true)
|
||||
let craftingTitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - craftingTitleSize.width) * 0.5), y: craftingOriginY), size: craftingTitleSize)
|
||||
if let craftingTitleView = self.craftingTitle.view {
|
||||
if craftingTitleView.superview == nil {
|
||||
transition.animateAlpha(view: craftingTitleView, from: 0.0, to: 1.0)
|
||||
transition.animateBlur(layer: craftingTitleView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
transition.animatePosition(view: craftingTitleView, from: CGPoint(x: 0.0, y: offset), to: .zero, additive: true)
|
||||
|
||||
self.addSubview(titleView)
|
||||
self.addSubview(craftingTitleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
craftingTitleView.frame = craftingTitleFrame
|
||||
}
|
||||
craftingOriginY += titleSize.height
|
||||
craftingOriginY += craftingTitleSize.height
|
||||
craftingOriginY += 7.0
|
||||
|
||||
let subtitleSize = self.craftingSubtitle.update(
|
||||
let craftingSubtitleSize = self.craftingSubtitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: giftTitle, font: Font.semibold(13.0), textColor: .white.withAlphaComponent(0.5))))
|
||||
|
|
@ -816,34 +828,30 @@ private final class CraftGiftPageContent: Component {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) * 0.5), y: craftingOriginY), size: subtitleSize)
|
||||
if let subtitleView = self.craftingSubtitle.view {
|
||||
if subtitleView.superview == nil {
|
||||
transition.animateAlpha(view: subtitleView, from: 0.0, to: 1.0)
|
||||
transition.animateBlur(layer: subtitleView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
transition.animatePosition(view: subtitleView, from: CGPoint(x: 0.0, y: offset), to: .zero, additive: true)
|
||||
let craftingSubtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - craftingSubtitleSize.width) * 0.5), y: craftingOriginY), size: craftingSubtitleSize)
|
||||
if let craftingSubtitleView = self.craftingSubtitle.view {
|
||||
if craftingSubtitleView.superview == nil {
|
||||
transition.animateAlpha(view: craftingSubtitleView, from: 0.0, to: 1.0)
|
||||
transition.animateBlur(layer: craftingSubtitleView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
transition.animatePosition(view: craftingSubtitleView, from: CGPoint(x: 0.0, y: offset), to: .zero, additive: true)
|
||||
|
||||
self.addSubview(subtitleView)
|
||||
self.addSubview(craftingSubtitleView)
|
||||
}
|
||||
subtitleView.frame = subtitleFrame
|
||||
craftingSubtitleView.frame = craftingSubtitleFrame
|
||||
}
|
||||
craftingOriginY += subtitleSize.height
|
||||
craftingOriginY += craftingSubtitleSize.height
|
||||
craftingOriginY += 21.0
|
||||
|
||||
let descriptionFont = Font.regular(13.0)
|
||||
let descriptionBoldFont = Font.semibold(13.0)
|
||||
let descriptionColor = UIColor.white.withAlphaComponent(0.5)
|
||||
let rawDescriptionString = "If crafting fails, all selected gifts\nwill be consumed."
|
||||
let rawDescriptionString = "If crafting fails, all used gifts\nwill be lost."
|
||||
let descriptionString = parseMarkdownIntoAttributedString(rawDescriptionString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionColor), linkAttribute: { _ in return nil })).mutableCopy() as! NSMutableAttributedString
|
||||
|
||||
let craftingDescriptionSize = self.craftingDescription.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: .white.withAlphaComponent(0.3),
|
||||
MultilineTextComponent(
|
||||
text: .plain(descriptionString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
|
|
@ -904,6 +912,165 @@ private final class CraftGiftPageContent: Component {
|
|||
}
|
||||
craftingProbabilityView.frame = craftingProbabilityFrame
|
||||
}
|
||||
} else {
|
||||
if let craftingTitleView = self.craftingTitle.view {
|
||||
transition.setAlpha(view: craftingTitleView, alpha: 0.0, completion: { _ in
|
||||
craftingTitleView.removeFromSuperview()
|
||||
})
|
||||
transition.animateBlur(layer: craftingTitleView.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
}
|
||||
if let craftingSubtitleView = self.craftingSubtitle.view {
|
||||
transition.setAlpha(view: craftingSubtitleView, alpha: 0.0, completion: { _ in
|
||||
craftingSubtitleView.removeFromSuperview()
|
||||
})
|
||||
transition.animateBlur(layer: craftingSubtitleView.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
}
|
||||
if let craftingDescriptionView = self.craftingDescription.view {
|
||||
transition.setAlpha(view: craftingDescriptionView, alpha: 0.0, completion: { _ in
|
||||
craftingDescriptionView.removeFromSuperview()
|
||||
})
|
||||
transition.animateBlur(layer: craftingDescriptionView.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
}
|
||||
if let craftingProbabilityView = self.craftingProbability.view {
|
||||
craftingProbabilityView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
craftingProbabilityView.removeFromSuperview()
|
||||
})
|
||||
transition.animateBlur(layer: craftingProbabilityView.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
}
|
||||
}
|
||||
|
||||
if component.displayState == .failure {
|
||||
var failureOriginY = craftContentHeight * 0.5 - 16.0
|
||||
let offset = -(craftContentHeight - originalCraftContentHeight)
|
||||
|
||||
let failureTitleSize = self.failureTitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Crafting Failed", font: Font.bold(20.0), textColor: UIColor(rgb: 0xff746d))))
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let failureTitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - failureTitleSize.width) * 0.5), y: failureOriginY), size: failureTitleSize)
|
||||
if let failureTitleView = self.failureTitle.view {
|
||||
if failureTitleView.superview == nil {
|
||||
transition.animateAlpha(view: failureTitleView, from: 0.0, to: 1.0)
|
||||
transition.animateBlur(layer: failureTitleView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
transition.animatePosition(view: failureTitleView, from: CGPoint(x: 0.0, y: offset), to: .zero, additive: true)
|
||||
|
||||
self.addSubview(failureTitleView)
|
||||
}
|
||||
failureTitleView.frame = failureTitleFrame
|
||||
}
|
||||
failureOriginY += failureTitleSize.height
|
||||
failureOriginY += 17.0
|
||||
|
||||
let descriptionFont = Font.regular(13.0)
|
||||
let descriptionBoldFont = Font.semibold(13.0)
|
||||
let descriptionColor = UIColor(rgb: 0xf7af8c)
|
||||
let rawDescriptionString = "This crafting attempt was unsuccessful.\n**\(component.selectedGiftIds.count) gifts** were lost."
|
||||
let descriptionString = parseMarkdownIntoAttributedString(rawDescriptionString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionColor), linkAttribute: { _ in return nil })).mutableCopy() as! NSMutableAttributedString
|
||||
|
||||
let failureDescriptionSize = self.failureDescription.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(descriptionString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let failureDescriptionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - failureDescriptionSize.width) * 0.5), y: failureOriginY), size: failureDescriptionSize)
|
||||
if let failureDescriptionView = self.failureDescription.view {
|
||||
if failureDescriptionView.superview == nil {
|
||||
transition.animateAlpha(view: failureDescriptionView, from: 0.0, to: 1.0)
|
||||
transition.animateBlur(layer: failureDescriptionView.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
transition.animatePosition(view: failureDescriptionView, from: CGPoint(x: 0.0, y: offset), to: .zero, additive: true)
|
||||
|
||||
self.addSubview(failureDescriptionView)
|
||||
}
|
||||
failureDescriptionView.frame = failureDescriptionFrame
|
||||
}
|
||||
failureOriginY += failureDescriptionSize.height
|
||||
failureOriginY += 34.0
|
||||
|
||||
var indices: [Int] = []
|
||||
for index in component.selectedGiftIds.keys.sorted() {
|
||||
indices.append(Int(index))
|
||||
}
|
||||
var lostGifts: [GiftItem] = []
|
||||
for index in indices {
|
||||
if let giftId = component.selectedGiftIds[Int32(index)], let gift = self.giftMap[giftId] {
|
||||
lostGifts.append(gift)
|
||||
}
|
||||
}
|
||||
|
||||
let itemSize = CGSize(width: 80.0, height: 80.0)
|
||||
let itemSpacing: CGFloat = 16.0
|
||||
var itemDelay: Double = 0.2
|
||||
|
||||
let totalItemsWidth: CGFloat = itemSize.width * CGFloat(lostGifts.count) + itemSpacing * CGFloat(lostGifts.count - 1)
|
||||
var itemOriginX: CGFloat = floor((availableSize.width - totalItemsWidth) / 2.0)
|
||||
|
||||
for gift in lostGifts {
|
||||
let itemId = AnyHashable(gift.gift.id)
|
||||
var itemTransition = transition
|
||||
let visibleItem: ComponentView<Empty>
|
||||
if let current = self.failedGifts[itemId] {
|
||||
visibleItem = current
|
||||
} else {
|
||||
visibleItem = ComponentView()
|
||||
self.failedGifts[itemId] = visibleItem
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
let ribbonText = "#\(gift.gift.number)"
|
||||
let ribbonColor: GiftItemComponent.Ribbon.Color = .custom(0xff645b, 0xff645b)
|
||||
|
||||
let _ = visibleItem.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
style: .glass,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift.gift, price: nil),
|
||||
ribbon: GiftItemComponent.Ribbon(text: ribbonText, font: .monospaced, color: ribbonColor, outline: nil),
|
||||
badge: nil,
|
||||
resellPrice: nil,
|
||||
isHidden: false,
|
||||
isSelected: false,
|
||||
isPinned: false,
|
||||
isEditing: false,
|
||||
mode: .grid,
|
||||
action: nil,
|
||||
contextAction: nil
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: itemSize
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: itemOriginX, y: failureOriginY), size: itemSize)
|
||||
if let itemView = visibleItem.view {
|
||||
if itemView.superview == nil {
|
||||
self.addSubview(itemView)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25, delay: itemDelay)
|
||||
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: itemDelay)
|
||||
}
|
||||
}
|
||||
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
||||
}
|
||||
itemOriginX += itemSize.width + itemSpacing
|
||||
itemDelay += 0.07
|
||||
}
|
||||
}
|
||||
|
||||
let tableSize = CGSize(width: availableSize.width, height: 320.0)
|
||||
|
|
@ -914,7 +1081,7 @@ private final class CraftGiftPageContent: Component {
|
|||
context: component.context,
|
||||
gifts: selectedGifts,
|
||||
buttonColor: component.colors.3,
|
||||
isCrafting: component.isCrafting,
|
||||
isCrafting: isCrafting,
|
||||
result: component.result,
|
||||
select: { [weak self] index in
|
||||
guard let self, let component = self.component, let environment = self.environment, let genericGift = self.starGiftsMap[component.gift.giftId], let resaleContext = component.resaleContext() else {
|
||||
|
|
@ -927,7 +1094,7 @@ private final class CraftGiftPageContent: Component {
|
|||
gift: component.gift,
|
||||
genericGift: genericGift,
|
||||
selectedGiftIds: Set(component.selectedGiftIds.values),
|
||||
starsTopUpOptions: self.starsTopUpOptionsPromise.get(),
|
||||
starsTopUpOptions: component.starsTopUpOptionsPromise.get(),
|
||||
selectGift: { [weak self] item in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
|
|
@ -947,11 +1114,11 @@ private final class CraftGiftPageContent: Component {
|
|||
self.component?.removeGift(index)
|
||||
},
|
||||
willFinish: { [weak self] success in
|
||||
guard let self else {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if !success {
|
||||
self.isFailing = true
|
||||
component.externalState.displayFailure = true
|
||||
}
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.5))
|
||||
},
|
||||
|
|
@ -972,13 +1139,7 @@ private final class CraftGiftPageContent: Component {
|
|||
controller.dismiss()
|
||||
})
|
||||
} else {
|
||||
if let navigationController = controller.navigationController {
|
||||
controller.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { _ in
|
||||
controller.dismiss()
|
||||
})
|
||||
let previousController = navigationController.viewControllers[max(0, navigationController.viewControllers.count - 2)]
|
||||
animateRipple(parentView: previousController.view, screenCornerRadius: environment.deviceMetrics.screenCornerRadius, location: CGPoint(x: previousController.view.bounds.midX, y: previousController.view.bounds.midY))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -986,7 +1147,7 @@ private final class CraftGiftPageContent: Component {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: tableSize.height)
|
||||
)
|
||||
let craftTableFrame = CGRect(origin: CGPoint(x: 0.0, y: component.isCrafting ? floor((component.screenSize.height - craftTableSize.height) / 2.0) : 10.0), size: craftTableSize)
|
||||
let craftTableFrame = CGRect(origin: CGPoint(x: 0.0, y: isCrafting && !"".isEmpty ? floor((component.screenSize.height - craftTableSize.height) / 2.0) : 10.0), size: craftTableSize)
|
||||
if let craftTableView = self.craftTable.view {
|
||||
if craftTableView.superview == nil {
|
||||
craftTableView.layer.cornerRadius = 40.0
|
||||
|
|
@ -997,7 +1158,7 @@ private final class CraftGiftPageContent: Component {
|
|||
transition.setFrame(view: craftTableView, frame: craftTableFrame)
|
||||
}
|
||||
|
||||
transition.setAlpha(view: self.infoContainer, alpha: component.displayCraftInfo ? 1.0 : 0.0)
|
||||
transition.setAlpha(view: self.infoContainer, alpha: component.displayInfo ? 1.0 : 0.0)
|
||||
|
||||
let infoHeaderSize = self.infoHeader.update(
|
||||
transition: transition,
|
||||
|
|
@ -1033,6 +1194,10 @@ private final class CraftGiftPageContent: Component {
|
|||
self.infoContainer.addSubview(infoHeaderView)
|
||||
}
|
||||
transition.setFrame(view: infoHeaderView, frame: infoHeaderFrame)
|
||||
|
||||
if self.subviews.last !== self.infoContainer {
|
||||
self.bringSubviewToFront(self.infoContainer)
|
||||
}
|
||||
}
|
||||
infoContentHeight += infoHeaderSize.height
|
||||
infoContentHeight += 16.0
|
||||
|
|
@ -1149,17 +1314,17 @@ private final class CraftGiftPageContent: Component {
|
|||
transition.setFrame(view: infoListView, frame: infoListFrame)
|
||||
}
|
||||
|
||||
if component.displayCraftInfo {
|
||||
if component.displayInfo {
|
||||
infoContentHeight += infoListSize.height
|
||||
infoContentHeight += 95.0
|
||||
}
|
||||
transition.setFrame(view: self.infoContainer, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: infoContentHeight)))
|
||||
|
||||
transition.setFrame(layer: self.background, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: craftContentHeight)))
|
||||
transition.setFrame(layer: self.overlay, frame: CGRect(origin: CGPoint(x: 0.0, y: component.isCrafting ? floor((component.screenSize.height - availableSize.width) / 2.0) : 169.0 - availableSize.width * 0.5), size: CGSize(width: availableSize.width, height: availableSize.width)))
|
||||
transition.setFrame(layer: self.overlay, frame: CGRect(origin: CGPoint(x: 0.0, y: isCrafting && !"".isEmpty ? floor((component.screenSize.height - availableSize.width) / 2.0) : 169.0 - availableSize.width * 0.5), size: CGSize(width: availableSize.width, height: availableSize.width)))
|
||||
|
||||
let effectiveContentHeight: CGFloat
|
||||
if component.displayCraftInfo {
|
||||
if component.displayInfo {
|
||||
effectiveContentHeight = infoContentHeight
|
||||
} else {
|
||||
effectiveContentHeight = craftContentHeight
|
||||
|
|
@ -1209,11 +1374,14 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
private let context: AccountContext
|
||||
private let giftId: Int64
|
||||
|
||||
var displayCraftInfo = false
|
||||
var displayInfo = false
|
||||
var isCrafting = false
|
||||
var inProgress = false
|
||||
var displayFailure = false
|
||||
var result: CraftTableComponent.Result?
|
||||
var selectedGiftIds: [Int32: Int64] = [:]
|
||||
|
||||
let starsTopUpOptionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||
|
||||
private var _resaleContext: ResaleGiftsContext?
|
||||
var resaleContext: ResaleGiftsContext {
|
||||
|
|
@ -1241,12 +1409,14 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
return
|
||||
}
|
||||
if count < 1 {
|
||||
self.displayCraftInfo = true
|
||||
self.displayInfo = true
|
||||
self.updated()
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementGiftCraftingTips(accountManager: context.sharedContext.accountManager).start()
|
||||
}
|
||||
})
|
||||
|
||||
self.starsTopUpOptionsPromise.set(context.engine.payments.starsTopUpOptions() |> map(Optional.init))
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -1269,6 +1439,13 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
let environment = context.environment[EnvironmentType.self]
|
||||
let state = context.state
|
||||
|
||||
let strings = environment.strings
|
||||
|
||||
if externalState.displayFailure {
|
||||
state.displayFailure = true
|
||||
state.inProgress = false
|
||||
}
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let craftContext = context.component.craftContext
|
||||
|
|
@ -1290,11 +1467,15 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
let theme = environment.theme
|
||||
|
||||
var colors: (UIColor, UIColor, UIColor, UIColor, UIColor) = (
|
||||
UIColor(rgb: 0x263245), UIColor(rgb: 0x232e3f), UIColor(rgb: 0x304059), UIColor(rgb: 0x425168), theme.list.itemCheckColors.fillColor
|
||||
UIColor(rgb: 0x263245),
|
||||
UIColor(rgb: 0x232e3f),
|
||||
UIColor(rgb: 0x304059),
|
||||
UIColor(rgb: 0x425168),
|
||||
theme.list.itemCheckColors.fillColor
|
||||
)
|
||||
var permilleValue: Int32 = 0
|
||||
for id in state.selectedGiftIds.values {
|
||||
if let gift = externalState.giftMap[id] {
|
||||
if let gift = externalState.giftsMap[id] {
|
||||
permilleValue += gift.gift.craftChancePermille ?? 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1303,34 +1484,47 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
colors.1 = UIColor(rgb: 0x1a2f38)
|
||||
colors.2 = UIColor(rgb: 0x22464a)
|
||||
colors.3 = UIColor(rgb: 0x2d4e50)
|
||||
if !state.displayCraftInfo {
|
||||
if !state.displayInfo {
|
||||
colors.4 = UIColor(rgb: 0x33bf54)
|
||||
}
|
||||
}
|
||||
if state.displayFailure {
|
||||
colors.0 = UIColor(rgb: 0x46231a)
|
||||
colors.1 = UIColor(rgb: 0x381b1a)
|
||||
colors.2 = UIColor(rgb: 0x51291f)
|
||||
colors.3 = UIColor(rgb: 0x683e34)
|
||||
if !state.displayInfo {
|
||||
colors.4 = UIColor(rgb: 0x683e34)
|
||||
}
|
||||
}
|
||||
|
||||
var buttonColor = colors.3
|
||||
if state.displayCraftInfo, let backdropAttribute = component.gift.attributes.first(where: { attribute in
|
||||
if state.displayInfo, let backdropAttribute = component.gift.attributes.first(where: { attribute in
|
||||
if case .backdrop = attribute {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
}), case let .backdrop(_, _, _, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
}
|
||||
|
||||
var backgroundColor = colors.1
|
||||
if state.displayCraftInfo {
|
||||
if state.displayInfo {
|
||||
backgroundColor = environment.theme.list.plainBackgroundColor
|
||||
}
|
||||
|
||||
let giftTitle = "\(component.gift.title) #\(formatCollectibleNumber(component.gift.number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
|
||||
let buttonContent: AnyComponentWithIdentity<Empty>
|
||||
if state.displayCraftInfo {
|
||||
if state.displayInfo {
|
||||
buttonContent = AnyComponentWithIdentity(id: "info", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Select Gifts", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor)))
|
||||
))
|
||||
} else if state.displayFailure {
|
||||
buttonContent = AnyComponentWithIdentity(id: "fail", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Craft Another Gift", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor)))
|
||||
))
|
||||
} else {
|
||||
var buttonAnimatedItems: [AnimatedTextComponent.Item] = []
|
||||
buttonAnimatedItems.append(AnimatedTextComponent.Item(id: "percent", content: .number(Int(permilleValue / 10), minDigits: 1)))
|
||||
|
|
@ -1364,6 +1558,15 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
))
|
||||
}
|
||||
|
||||
var displayState: CraftGiftPageContent.DisplayState = .default
|
||||
if state.displayFailure {
|
||||
displayState = .failure
|
||||
} else if state.isCrafting {
|
||||
displayState = .crafting
|
||||
}
|
||||
|
||||
let hideButtons = displayState == .crafting
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: ResizableSheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(
|
||||
|
|
@ -1376,12 +1579,12 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
colors: colors,
|
||||
gift: component.gift,
|
||||
selectedGiftIds: state.selectedGiftIds,
|
||||
displayCraftInfo: state.displayCraftInfo,
|
||||
isCrafting: state.isCrafting,
|
||||
inProgress: state.inProgress,
|
||||
displayState: displayState,
|
||||
displayInfo: state.displayInfo,
|
||||
result: state.result,
|
||||
screenSize: context.availableSize,
|
||||
externalState: externalState,
|
||||
starsTopUpOptionsPromise: state.starsTopUpOptionsPromise,
|
||||
selectGift: { [weak state] index, gift in
|
||||
guard let state else {
|
||||
return
|
||||
|
|
@ -1401,7 +1604,7 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
}
|
||||
)
|
||||
),
|
||||
leftItem: state.isCrafting ? nil : AnyComponent(
|
||||
leftItem: hideButtons ? nil : AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: buttonColor,
|
||||
|
|
@ -1418,7 +1621,7 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
}
|
||||
)
|
||||
),
|
||||
rightItem: state.isCrafting || state.displayCraftInfo ? nil : AnyComponent(
|
||||
rightItem: hideButtons || state.displayInfo ? nil : AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: buttonColor,
|
||||
|
|
@ -1431,16 +1634,16 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
)
|
||||
)),
|
||||
action: { [weak state] _ in
|
||||
guard let state, !state.isCrafting else {
|
||||
guard let state, !state.inProgress else {
|
||||
return
|
||||
}
|
||||
state.displayCraftInfo = !state.displayCraftInfo
|
||||
state.displayInfo = !state.displayInfo
|
||||
state.updated(transition: .spring(duration: 0.3))
|
||||
}
|
||||
)
|
||||
),
|
||||
hasTopEdgeEffect: false,
|
||||
bottomItem: state.isCrafting ? nil : AnyComponent(
|
||||
bottomItem: hideButtons ? nil : AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
|
|
@ -1449,15 +1652,33 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: buttonContent,
|
||||
isEnabled: state.displayCraftInfo ? true : state.selectedGiftIds.count > 0,
|
||||
isEnabled: state.displayInfo ? true : state.selectedGiftIds.count > 0,
|
||||
displaysProgress: state.inProgress,
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
if state.displayCraftInfo {
|
||||
state.displayCraftInfo = false
|
||||
if state.displayInfo {
|
||||
state.displayInfo = false
|
||||
state.updated(transition: .spring(duration: 0.3))
|
||||
} else if state.displayFailure, let genericGift = externalState.starGiftsMap[component.gift.giftId] {
|
||||
let selectController = SelectCraftGiftScreen(
|
||||
context: component.context,
|
||||
craftContext: component.craftContext,
|
||||
resaleContext: state.resaleContext,
|
||||
gift: component.gift,
|
||||
genericGift: genericGift,
|
||||
selectedGiftIds: Set(),
|
||||
starsTopUpOptions: state.starsTopUpOptionsPromise.get(),
|
||||
selectGift: { item in
|
||||
let craftController = GiftCraftScreen(context: component.context, gift: item.gift)
|
||||
if let controller = controller() as? GiftCraftScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||
controller.dismissAnimated()
|
||||
navigationController.pushViewController(craftController)
|
||||
}
|
||||
}
|
||||
)
|
||||
environment.controller()?.push(selectController)
|
||||
} else {
|
||||
state.inProgress = true
|
||||
state.updated(transition: .spring(duration: 0.3))
|
||||
|
|
@ -1481,7 +1702,7 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
}
|
||||
var references: [StarGiftReference] = []
|
||||
for index in indices {
|
||||
if let giftId = state.selectedGiftIds[Int32(index)], let gift = externalState.giftMap[giftId] {
|
||||
if let giftId = state.selectedGiftIds[Int32(index)], let gift = externalState.giftsMap[giftId] {
|
||||
references.append(gift.reference)
|
||||
}
|
||||
}
|
||||
|
|
@ -1512,10 +1733,14 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
state.isCrafting = true
|
||||
state.result = .fail
|
||||
state.updated(transition: .spring(duration: 0.8))
|
||||
|
||||
Queue.mainQueue().after(1.0) {
|
||||
craftContext.reload()
|
||||
}
|
||||
default:
|
||||
if let navigationController = controller()?.navigationController {
|
||||
dismiss(true)
|
||||
let alertController = textAlertController(context: component.context, title: nil, text: "Unknown Error", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})])
|
||||
let alertController = textAlertController(context: component.context, title: nil, text: strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
|
||||
(navigationController.topViewController as? ViewController)?.present(alertController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
|
@ -1525,7 +1750,7 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
)
|
||||
),
|
||||
backgroundColor: .color(backgroundColor),
|
||||
isFullscreen: state.isCrafting,
|
||||
isFullscreen: false, //state.isCrafting,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
|
|
|
|||
|
|
@ -379,6 +379,7 @@ final class SelectGiftPageContent: Component {
|
|||
starsContext: component.context.starsContext!,
|
||||
peerId: component.context.account.peerId,
|
||||
gift: component.genericGift,
|
||||
isPlain: true,
|
||||
confirmPurchaseImmediately: true,
|
||||
starsTopUpOptions: component.starsTopUpOptions,
|
||||
scrollToTop: {},
|
||||
|
|
@ -530,9 +531,9 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "back", component: AnyComponent(
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Back",
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ swift_library(
|
|||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SheetComponent",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import Markdown
|
||||
|
||||
final class BalanceComponent: Component {
|
||||
private let context: AccountContext
|
||||
private let theme: PresentationTheme
|
||||
private let action: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: BalanceComponent, rhs: BalanceComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private var text = ComponentView<Empty>()
|
||||
|
||||
private var component: BalanceComponent?
|
||||
private var componentState: EmptyComponentState?
|
||||
|
||||
private var starsBalance: Int64 = 0
|
||||
private var tonBalance: Int64 = 0
|
||||
private var balanceDisposable: Disposable?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.balanceDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
private var isUpdating = false
|
||||
func update(component: BalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
if self.balanceDisposable == nil {
|
||||
if let starsContext = component.context.starsContext, let tonContext = component.context.tonContext {
|
||||
self.balanceDisposable = combineLatest(queue: Queue.mainQueue(),
|
||||
starsContext.state,
|
||||
tonContext.state
|
||||
).start(next: { [weak self] starsState, tonState in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.starsBalance = starsState?.balance.value ?? 0
|
||||
self.tonBalance = tonState?.balance.value ?? 0
|
||||
if !self.isUpdating {
|
||||
self.componentState?.updated()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var rawString: String = ""
|
||||
let starsBalanceString = "**⭐️\(presentationStringsFormattedNumber(Int32(clamping: self.starsBalance), presentationData.dateTimeFormat.groupingSeparator))**"
|
||||
if self.tonBalance > 0 {
|
||||
let tonBalanceString = "**💎\(formatTonAmountText(self.tonBalance, dateTimeFormat: presentationData.dateTimeFormat))**"
|
||||
rawString = starsBalanceString + "\n" + tonBalanceString
|
||||
} else {
|
||||
rawString = presentationData.strings.Stars_Purchase_Balance + "\n" + starsBalanceString
|
||||
}
|
||||
|
||||
let attributedText = parseMarkdownIntoAttributedString(
|
||||
rawString,
|
||||
attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
),
|
||||
textAlignment: .right
|
||||
).mutableCopy() as! NSMutableAttributedString
|
||||
let starRange = (attributedText.string as NSString).range(of: "⭐️")
|
||||
if starRange.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: starRange)
|
||||
attributedText.addAttribute(.baselineOffset, value: 1.0, range: starRange)
|
||||
}
|
||||
let tonRange = (attributedText.string as NSString).range(of: "💎")
|
||||
if tonRange.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: tonRange)
|
||||
attributedText.addAttribute(.baselineOffset, value: 1.0, range: tonRange)
|
||||
}
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
text: .plain(attributedText),
|
||||
horizontalAlignment: .right,
|
||||
maximumNumberOfLines: 2,
|
||||
lineSpacing: 0.1,
|
||||
displaysAsynchronously: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let inset: CGFloat = 12.0
|
||||
let size = CGSize(width: textSize.width + inset * 2.0, height: 44.0)
|
||||
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
textView.isUserInteractionEnabled = false
|
||||
self.addSubview(textView)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
transition.animateAlpha(view: textView, from: 0.0, to: 1.0)
|
||||
}
|
||||
}
|
||||
textView.frame = CGRect(origin: CGPoint(x: inset, y: 8.0 - UIScreenPixel), size: textSize)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import LottieComponent
|
|||
import GiftLoadingShimmerView
|
||||
import EdgeEffect
|
||||
import GlassBackgroundComponent
|
||||
import ContextUI
|
||||
|
||||
private let minimumCountToDisplayFilters = 18
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
let starsContext: StarsContext
|
||||
let peerId: EnginePeer.Id
|
||||
let gift: StarGift.Gift
|
||||
let isPlain: Bool
|
||||
let confirmPurchaseImmediately: Bool
|
||||
let starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>?
|
||||
let scrollToTop: () -> Void
|
||||
|
|
@ -64,6 +66,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
starsContext: StarsContext,
|
||||
peerId: EnginePeer.Id,
|
||||
gift: StarGift.Gift,
|
||||
isPlain: Bool,
|
||||
confirmPurchaseImmediately: Bool,
|
||||
starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>?,
|
||||
scrollToTop: @escaping () -> Void,
|
||||
|
|
@ -82,6 +85,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
self.starsContext = starsContext
|
||||
self.peerId = peerId
|
||||
self.gift = gift
|
||||
self.isPlain = isPlain
|
||||
self.confirmPurchaseImmediately = confirmPurchaseImmediately
|
||||
self.starsTopUpOptions = starsTopUpOptions
|
||||
self.scrollToTop = scrollToTop
|
||||
|
|
@ -1075,7 +1079,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
|
||||
let loadingSize = CGSize(width: availableSize.width, height: min(1000.0, availableSize.height))
|
||||
if isLoading && self.showLoading {
|
||||
self.loadingView.update(size: loadingSize, theme: component.theme, showFilters: !showingFilters, isPlain: true, transition: .immediate)
|
||||
self.loadingView.update(size: loadingSize, theme: component.theme, showFilters: !showingFilters, isPlain: component.isPlain, transition: .immediate)
|
||||
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
|
||||
} else {
|
||||
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
|
||||
|
|
@ -1145,10 +1149,8 @@ final class GiftStoreScreenComponent: Component {
|
|||
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
|
||||
private let balanceBackgroundView: GlassBackgroundView
|
||||
private let balanceTitle = ComponentView<Empty>()
|
||||
private let balanceValue = ComponentView<Empty>()
|
||||
private let balanceIcon = ComponentView<Empty>()
|
||||
private let balance = ComponentView<Empty>()
|
||||
private let balanceBackgroundView: GlassContextExtractableContainer
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
|
|
@ -1165,7 +1167,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
private var isUpdating: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.balanceBackgroundView = GlassBackgroundView()
|
||||
self.balanceBackgroundView = GlassContextExtractableContainer()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
|
|
@ -1211,6 +1213,68 @@ final class GiftStoreScreenComponent: Component {
|
|||
contentView.updateScrolling(bounds: bounds, interactive: interactive, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func presentBalanceMenu() {
|
||||
guard let component = self.component, let starsContext = component.context.starsContext, let tonContext = component.context.tonContext, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
let tonBalance = tonContext.currentState?.balance.value ?? 0
|
||||
if tonBalance == 0 {
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
|
||||
controller.push(controller)
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let sourceView = self.balanceBackgroundView
|
||||
|
||||
let items: Signal<[ContextMenuItem], NoError> = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
starsContext.state,
|
||||
tonContext.state
|
||||
)
|
||||
|> take(1)
|
||||
|> map { starsState, tonState -> [ContextMenuItem] in
|
||||
let starsBalance = starsState?.balance ?? .zero
|
||||
let tonBalance = tonState?.balance.value ?? 0
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: "My Stars",
|
||||
textLayout: .secondLineWithValue(formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)),
|
||||
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stars"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
|
||||
environment.controller()?.push(controller)
|
||||
}
|
||||
)))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: "My TON",
|
||||
textLayout: .secondLineWithValue(formatTonAmountText(tonBalance, dateTimeFormat: presentationData.dateTimeFormat)),
|
||||
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Ton"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
|
||||
environment.controller()?.push(controller)
|
||||
}
|
||||
)))
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: nil)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func update(component: GiftStoreScreenComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
|
|
@ -1255,42 +1319,37 @@ final class GiftStoreScreenComponent: Component {
|
|||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition)
|
||||
|
||||
let balanceTitleSize = self.balanceTitle.update(
|
||||
|
||||
|
||||
let balanceSize = self.balance.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: strings.Stars_Purchase_Balance,
|
||||
font: Font.regular(14.0),
|
||||
textColor: environment.theme.actionSheet.primaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
component: AnyComponent(
|
||||
BalanceComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentBalanceMenu()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat)
|
||||
let smallLabelFont = Font.regular(11.0)
|
||||
let labelFont = Font.semibold(14.0)
|
||||
let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator)
|
||||
|
||||
let balanceValueSize = self.balanceValue.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(balanceText),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let balanceIconSize = self.balanceIcon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let balanceFrame = CGRect(origin: .zero, size: balanceSize)
|
||||
if let balanceView = self.balance.view {
|
||||
if balanceView.superview == nil {
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceView)
|
||||
}
|
||||
balanceView.frame = balanceFrame
|
||||
|
||||
let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceSize)
|
||||
|
||||
transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame)
|
||||
self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||
}
|
||||
if self.balanceBackgroundView.superview == nil {
|
||||
component.overNavigationContainer.addSubview(self.balanceBackgroundView)
|
||||
}
|
||||
|
|
@ -1300,32 +1359,6 @@ final class GiftStoreScreenComponent: Component {
|
|||
topInset = environment.statusBarHeight - 6.0
|
||||
}
|
||||
|
||||
if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view {
|
||||
if balanceTitleView.superview == nil {
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceTitleView)
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceValueView)
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceIconView)
|
||||
}
|
||||
|
||||
let topBalanceOriginY = (44.0 - balanceTitleSize.height - balanceValueSize.height) / 2.0
|
||||
|
||||
let balanceSideInset: CGFloat = 12.0
|
||||
var balanceBackgroundSize = CGSize(width: balanceTitleSize.width + balanceSideInset * 2.0, height: 44.0)
|
||||
balanceBackgroundSize.width = max(balanceBackgroundSize.width, balanceValueSize.width + balanceIconSize.width + 2.0 + balanceSideInset * 2.0)
|
||||
|
||||
let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceBackgroundSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceBackgroundSize)
|
||||
|
||||
transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame)
|
||||
self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: transition)
|
||||
|
||||
balanceTitleView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0)
|
||||
balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize)
|
||||
balanceValueView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0)
|
||||
balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize)
|
||||
balanceIconView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel)
|
||||
balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
|
|
@ -1360,6 +1393,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
starsContext: component.starsContext,
|
||||
peerId: component.peerId,
|
||||
gift: component.gift,
|
||||
isPlain: false,
|
||||
confirmPurchaseImmediately: false,
|
||||
starsTopUpOptions: nil,
|
||||
scrollToTop: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -746,8 +746,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -294,8 +294,8 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -739,7 +739,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
|
||||
badgeColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
let outer = UIColor(rgb: UInt32(bitPattern: outerColor))
|
||||
|
|
|
|||
|
|
@ -748,7 +748,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
|
||||
self.subject = .profileGift(peerId, gift.withGift(.unique(uniqueGift.withAttributes(updatedAttributes))))
|
||||
case let .message(message):
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt) = action.action, case let .unique(uniqueGift) = gift {
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt, isCrafted) = action.action, case let .unique(uniqueGift) = gift {
|
||||
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
|
||||
let updatedMedia: [Media] = [
|
||||
TelegramMediaAction(
|
||||
|
|
@ -770,7 +770,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
dropOriginalDetailsStars: nil,
|
||||
assigned: assigned,
|
||||
fromOffer: fromOffer,
|
||||
canCraftAt: canCraftAt
|
||||
canCraftAt: canCraftAt,
|
||||
isCrafted: isCrafted
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
@ -2909,8 +2910,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
var buttonColor: UIColor = UIColor.white.withAlphaComponent(0.16)
|
||||
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||
buttonColor = previewPatternColor
|
||||
if let backgroundColor = giftCompositionExternalState.backgroundColor {
|
||||
buttonColor = backgroundColor.mixedWith(.white, alpha: 0.2)
|
||||
}
|
||||
|
||||
let variantsMeasureDescription = variantsMeasureDescription.update(
|
||||
|
|
@ -5129,10 +5130,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonsBackground = .color(UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05))
|
||||
} else if showUpgradePreview, let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||
buttonsBackground = .color(previewPatternColor.withMultipliedBrightnessBy(1.05))
|
||||
}), case let .backdrop(_, _, _, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonsBackground = .color(UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2))
|
||||
} else if showUpgradePreview, let backgroundColor = giftCompositionExternalState.backgroundColor {
|
||||
buttonsBackground = .color(backgroundColor.mixedWith(.white, alpha: 0.2))
|
||||
}
|
||||
|
||||
var isBackButton = false
|
||||
|
|
@ -5449,7 +5450,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
|
||||
let fromPeerId = senderId ?? message.author?.id
|
||||
return (message.id.peerId, fromPeerId, message.author?.debugDisplayTitle, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate, nil, toPeerId, number, nil)
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftDate):
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftDate, _):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
|
|||
|
|
@ -3734,7 +3734,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
} else if case let .gift(gift) = subject {
|
||||
isGift = true
|
||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil, assigned: false, fromOffer: false, canCraftAt: nil))]
|
||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil, assigned: false, fromOffer: false, canCraftAt: nil, isCrafted: false))]
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
messages = .single([message])
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
}
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
|
@ -225,6 +227,9 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
|
|
@ -297,6 +302,20 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.item?.openSetCustom?()
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: MessagePriceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
|
||||
|
|
@ -329,6 +348,7 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.highlightNode.backgroundColor = item.theme.list.itemSearchHighlightColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
|
|
@ -370,6 +390,7 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.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 - params.rightInset - separatorRightInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -587,8 +587,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||
self.avatarPatternContentLayers.append(itemLayer)
|
||||
}
|
||||
|
||||
//itemLayer.frame = itemFrame
|
||||
transition.setFrame(layer: itemLayer, frame: itemFrame)
|
||||
itemLayer.frame = itemFrame
|
||||
itemLayer.layerTintColor = UIColor(white: 0.0, alpha: 0.8).cgColor
|
||||
transition.setAlpha(layer: itemLayer, alpha: 1.0 - itemScaleFraction)
|
||||
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ public final class TextFieldComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
guard let component = self.component else {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "star (3).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/star (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/star (3).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ton (3).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/ton (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/ton (3).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "giftcrash.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -15,16 +15,15 @@ import ChatControllerInteraction
|
|||
|
||||
extension ChatControllerImpl {
|
||||
var keyShortcutsInternal: [KeyShortcut] {
|
||||
if !self.traceVisibility() || !isTopmostChatController(self) {
|
||||
if !isTopmostChatController(self) {
|
||||
return []
|
||||
}
|
||||
|
||||
let strings = self.presentationData.strings
|
||||
|
||||
var inputShortcuts: [KeyShortcut]
|
||||
var inputShortcuts: [KeyShortcut] = []
|
||||
if self.chatDisplayNode.isInputViewFocused {
|
||||
inputShortcuts = [
|
||||
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {}),
|
||||
KeyShortcut(input: "B", modifiers: [.command], action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
|
|
@ -80,6 +79,16 @@ extension ChatControllerImpl {
|
|||
}
|
||||
})
|
||||
]
|
||||
|
||||
if self.context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
|
||||
inputShortcuts.append(
|
||||
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", modifiers: [.command], action: {})
|
||||
)
|
||||
} else {
|
||||
inputShortcuts.append(
|
||||
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {})
|
||||
)
|
||||
}
|
||||
} else if UIResponder.currentFirst() == nil {
|
||||
inputShortcuts = [
|
||||
KeyShortcut(title: strings.KeyCommand_FocusOnInputField, input: "\r", action: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -1136,7 +1136,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||
let sendGiftTitle: String
|
||||
var isIncoming = message.effectivelyIncoming(context.account.peerId)
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action {
|
||||
if isUpgrade && message.author?.id == context.account.peerId {
|
||||
isIncoming = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,6 +255,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
public let currentMediaDisplaySettings: Atomic<MediaDisplaySettings>
|
||||
private var mediaDisplaySettingsDisposable: Disposable?
|
||||
|
||||
public let currentChatSettings: Atomic<ChatSettings>
|
||||
private var chatSettingsDisposable: Disposable?
|
||||
|
||||
public let currentStickerSettings: Atomic<StickerSettings>
|
||||
private var stickerSettingsDisposable: Disposable?
|
||||
|
||||
|
|
@ -339,6 +342,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings)
|
||||
self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings)
|
||||
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
|
||||
self.currentChatSettings = Atomic(value: initialPresentationDataAndSettings.chatSettings)
|
||||
|
||||
if automaticEnergyUsageShouldBeOnNow(settings: self.currentAutomaticMediaDownloadSettings) {
|
||||
self.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
|
||||
|
|
@ -485,6 +489,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
}
|
||||
})
|
||||
|
||||
self.chatSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.chatSettings])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
|
||||
if let strongSelf = self {
|
||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) {
|
||||
let _ = strongSelf.currentChatSettings.swap(settings)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let immediateExperimentalUISettingsValue = self.immediateExperimentalUISettingsValue
|
||||
let _ = immediateExperimentalUISettingsValue.swap(initialPresentationDataAndSettings.experimentalUISettings)
|
||||
|
||||
|
|
@ -1078,6 +1091,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
self.inAppNotificationSettingsDisposable?.dispose()
|
||||
self.mediaInputSettingsDisposable?.dispose()
|
||||
self.mediaDisplaySettingsDisposable?.dispose()
|
||||
self.chatSettingsDisposable?.dispose()
|
||||
self.stickerSettingsDisposable?.dispose()
|
||||
self.callDisposable?.dispose()
|
||||
self.groupCallDisposable?.dispose()
|
||||
self.callStateDisposable?.dispose()
|
||||
|
|
|
|||
49
submodules/TelegramUIPreferences/Sources/ChatSettings.swift
Normal file
49
submodules/TelegramUIPreferences/Sources/ChatSettings.swift
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import Foundation
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
||||
public struct ChatSettings: Codable, Equatable {
|
||||
public let sendWithCmdEnter: Bool
|
||||
|
||||
public static var defaultSettings: ChatSettings {
|
||||
return ChatSettings(sendWithCmdEnter: false)
|
||||
}
|
||||
|
||||
public init(sendWithCmdEnter: Bool) {
|
||||
self.sendWithCmdEnter = sendWithCmdEnter
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.sendWithCmdEnter = (try container.decode(Int32.self, forKey: "sendWithCmdEnter")) != 0
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode((self.sendWithCmdEnter ? 1 : 0) as Int32, forKey: "sendWithCmdEnter")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatSettings, rhs: ChatSettings) -> Bool {
|
||||
return lhs.sendWithCmdEnter == rhs.sendWithCmdEnter
|
||||
}
|
||||
|
||||
public func withUpdatedSendWithCmdEnter(_ sendWithCmdEnter: Bool) -> ChatSettings {
|
||||
return ChatSettings(sendWithCmdEnter: sendWithCmdEnter)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChatSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (ChatSettings) -> ChatSettings) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.chatSettings, { entry in
|
||||
let currentSettings: ChatSettings
|
||||
if let entry = entry?.get(ChatSettings.self) {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = ChatSettings.defaultSettings
|
||||
}
|
||||
return SharedPreferencesEntry(f(currentSettings))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
|||
case drawingSettings = 19
|
||||
case mediaDisplaySettings = 20
|
||||
case updateSettings = 21
|
||||
case chatSettings = 22
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificSharedDataKeys {
|
||||
|
|
@ -68,6 +69,7 @@ public struct ApplicationSpecificSharedDataKeys {
|
|||
public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue)
|
||||
public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue)
|
||||
public static let updateSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.updateSettings.rawValue)
|
||||
public static let chatSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.chatSettings.rawValue)
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue