mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-07 14:53:35 +00:00
Merge commit 'a30ab38ce41f3389fee9054eb99875382365a6b7' into beta
This commit is contained in:
commit
4175c9f3b3
@ -568,6 +568,7 @@ public enum PeerInfoControllerMode {
|
|||||||
case forumTopic(thread: ChatReplyThreadMessage)
|
case forumTopic(thread: ChatReplyThreadMessage)
|
||||||
case recommendedChannels
|
case recommendedChannels
|
||||||
case myProfile
|
case myProfile
|
||||||
|
case myProfileGifts
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContactListActionItemInlineIconPosition {
|
public enum ContactListActionItemInlineIconPosition {
|
||||||
@ -975,6 +976,8 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
|
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||||
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
|
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
|
||||||
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
|
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
|
||||||
|
|
||||||
|
func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController
|
||||||
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController
|
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController
|
||||||
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
|
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
|
||||||
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
||||||
|
|||||||
@ -102,7 +102,7 @@ final class CameraDeviceContext {
|
|||||||
return 30.0
|
return 30.0
|
||||||
}
|
}
|
||||||
switch DeviceModel.current {
|
switch DeviceModel.current {
|
||||||
case .iPhone15ProMax, .iPhone14ProMax, .iPhone13ProMax, .iPhone16ProMax:
|
case .iPhone15ProMax, .iPhone14ProMax, .iPhone13ProMax:
|
||||||
return 60.0
|
return 60.0
|
||||||
default:
|
default:
|
||||||
return 30.0
|
return 30.0
|
||||||
|
|||||||
@ -34,10 +34,6 @@ public extension Camera {
|
|||||||
self = .iPhone15Pro
|
self = .iPhone15Pro
|
||||||
case .iPhone15ProMax:
|
case .iPhone15ProMax:
|
||||||
self = .iPhone15ProMax
|
self = .iPhone15ProMax
|
||||||
case .iPhone16Pro:
|
|
||||||
self = .iPhone15Pro
|
|
||||||
case .iPhone16ProMax:
|
|
||||||
self = .iPhone15ProMax
|
|
||||||
case .unknown:
|
case .unknown:
|
||||||
self = .unknown
|
self = .unknown
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -36,8 +36,6 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
case iPhone14ProZoomed
|
case iPhone14ProZoomed
|
||||||
case iPhone14ProMax
|
case iPhone14ProMax
|
||||||
case iPhone14ProMaxZoomed
|
case iPhone14ProMaxZoomed
|
||||||
case iPhone16Pro
|
|
||||||
case iPhone16ProMax
|
|
||||||
case iPad
|
case iPad
|
||||||
case iPadMini
|
case iPadMini
|
||||||
case iPad102Inch
|
case iPad102Inch
|
||||||
@ -70,8 +68,6 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
.iPhone14ProZoomed,
|
.iPhone14ProZoomed,
|
||||||
.iPhone14ProMax,
|
.iPhone14ProMax,
|
||||||
.iPhone14ProMaxZoomed,
|
.iPhone14ProMaxZoomed,
|
||||||
.iPhone16Pro,
|
|
||||||
.iPhone16ProMax,
|
|
||||||
.iPad,
|
.iPad,
|
||||||
.iPadMini,
|
.iPadMini,
|
||||||
.iPad102Inch,
|
.iPad102Inch,
|
||||||
@ -175,10 +171,6 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return CGSize(width: 430.0, height: 932.0)
|
return CGSize(width: 430.0, height: 932.0)
|
||||||
case .iPhone14ProMaxZoomed:
|
case .iPhone14ProMaxZoomed:
|
||||||
return CGSize(width: 375.0, height: 812.0)
|
return CGSize(width: 375.0, height: 812.0)
|
||||||
case .iPhone16Pro:
|
|
||||||
return CGSize(width: 402.0, height: 874.0)
|
|
||||||
case .iPhone16ProMax:
|
|
||||||
return CGSize(width: 440.0, height: 956.0)
|
|
||||||
case .iPad:
|
case .iPad:
|
||||||
return CGSize(width: 768.0, height: 1024.0)
|
return CGSize(width: 768.0, height: 1024.0)
|
||||||
case .iPadMini:
|
case .iPadMini:
|
||||||
@ -212,8 +204,6 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 53.0 + UIScreenPixel
|
return 53.0 + UIScreenPixel
|
||||||
case .iPhone14Pro, .iPhone14ProMax:
|
case .iPhone14Pro, .iPhone14ProMax:
|
||||||
return 55.0
|
return 55.0
|
||||||
case .iPhone16Pro, .iPhone16ProMax:
|
|
||||||
return 55.0
|
|
||||||
case let .unknown(_, _, _, screenCornerRadius):
|
case let .unknown(_, _, _, screenCornerRadius):
|
||||||
return screenCornerRadius
|
return screenCornerRadius
|
||||||
default:
|
default:
|
||||||
@ -223,7 +213,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
|
|
||||||
func safeInsets(inLandscape: Bool) -> UIEdgeInsets {
|
func safeInsets(inLandscape: Bool) -> UIEdgeInsets {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax:
|
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return inLandscape ? UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0)
|
return inLandscape ? UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
default:
|
default:
|
||||||
return UIEdgeInsets.zero
|
return UIEdgeInsets.zero
|
||||||
@ -232,7 +222,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
|
|
||||||
public func onScreenNavigationHeight(inLandscape: Bool, systemOnScreenNavigationHeight: CGFloat?) -> CGFloat? {
|
public func onScreenNavigationHeight(inLandscape: Bool, systemOnScreenNavigationHeight: CGFloat?) -> CGFloat? {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax, .iPhone16Pro, .iPhone16ProMax:
|
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax:
|
||||||
return inLandscape ? 21.0 : 34.0
|
return inLandscape ? 21.0 : 34.0
|
||||||
case .iPhone14ProZoomed:
|
case .iPhone14ProZoomed:
|
||||||
return inLandscape ? 21.0 : 28.0
|
return inLandscape ? 21.0 : 28.0
|
||||||
@ -272,8 +262,6 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 54.0
|
return 54.0
|
||||||
case .iPhone14ProMaxZoomed:
|
case .iPhone14ProMaxZoomed:
|
||||||
return 47.0
|
return 47.0
|
||||||
case .iPhone16Pro, .iPhone16ProMax:
|
|
||||||
return 54.0
|
|
||||||
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax:
|
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax:
|
||||||
return 44.0
|
return 44.0
|
||||||
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
||||||
@ -292,7 +280,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 162.0
|
return 162.0
|
||||||
case .iPhone6, .iPhone6Plus:
|
case .iPhone6, .iPhone6Plus:
|
||||||
return 163.0
|
return 163.0
|
||||||
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax:
|
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return 172.0
|
return 172.0
|
||||||
case .iPad, .iPad102Inch, .iPadPro10Inch:
|
case .iPad, .iPad102Inch, .iPadPro10Inch:
|
||||||
return 348.0
|
return 348.0
|
||||||
@ -311,9 +299,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 216.0
|
return 216.0
|
||||||
case .iPhone6Plus:
|
case .iPhone6Plus:
|
||||||
return 226.0
|
return 226.0
|
||||||
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed, .iPhone16Pro:
|
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
|
||||||
return 292.0
|
return 292.0
|
||||||
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax, .iPhone16ProMax:
|
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
|
||||||
return 302.0
|
return 302.0
|
||||||
case .iPad, .iPad102Inch, .iPadPro10Inch:
|
case .iPad, .iPad102Inch, .iPadPro10Inch:
|
||||||
return 263.0
|
return 263.0
|
||||||
@ -332,7 +320,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
func predictiveInputHeight(inLandscape: Bool) -> CGFloat {
|
func predictiveInputHeight(inLandscape: Bool) -> CGFloat {
|
||||||
if inLandscape {
|
if inLandscape {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax:
|
case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return 37.0
|
return 37.0
|
||||||
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
||||||
return 50.0
|
return 50.0
|
||||||
@ -343,7 +331,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .iPhone4, .iPhone5:
|
case .iPhone4, .iPhone5:
|
||||||
return 37.0
|
return 37.0
|
||||||
case .iPhone6, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax:
|
case .iPhone6, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return 44.0
|
return 44.0
|
||||||
case .iPhone6Plus:
|
case .iPhone6Plus:
|
||||||
return 45.0
|
return 45.0
|
||||||
@ -370,7 +358,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
|
|
||||||
public var hasDynamicIsland: Bool {
|
public var hasDynamicIsland: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax:
|
case .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -534,6 +534,14 @@ NSString *suffix = @"";
|
|||||||
return @"iPhone 15 Pro";
|
return @"iPhone 15 Pro";
|
||||||
if ([platform isEqualToString:@"iPhone16,2"])
|
if ([platform isEqualToString:@"iPhone16,2"])
|
||||||
return @"iPhone 15 Pro Max";
|
return @"iPhone 15 Pro Max";
|
||||||
|
if ([platform isEqualToString:@"iPhone17,3"])
|
||||||
|
return @"iPhone 16";
|
||||||
|
if ([platform isEqualToString:@"iPhone17,4"])
|
||||||
|
return @"iPhone 16 Plus";
|
||||||
|
if ([platform isEqualToString:@"iPhone17,1"])
|
||||||
|
return @"iPhone 16 Pro";
|
||||||
|
if ([platform isEqualToString:@"iPhone17,2"])
|
||||||
|
return @"iPhone 16 Pro Max";
|
||||||
|
|
||||||
if ([platform hasPrefix:@"iPod1"])
|
if ([platform hasPrefix:@"iPod1"])
|
||||||
return @"iPod touch 1G";
|
return @"iPod touch 1G";
|
||||||
|
|||||||
@ -67,7 +67,7 @@ struct PasscodeKeyboardLayout {
|
|||||||
self.topOffset = 226.0
|
self.topOffset = 226.0
|
||||||
self.biometricsOffset = 30.0
|
self.biometricsOffset = 30.0
|
||||||
self.deleteOffset = 20.0
|
self.deleteOffset = 20.0
|
||||||
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed, .iPhone16Pro:
|
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
|
||||||
self.buttonSize = 75.0
|
self.buttonSize = 75.0
|
||||||
self.horizontalSecond = 103.0
|
self.horizontalSecond = 103.0
|
||||||
self.horizontalThird = 206.0
|
self.horizontalThird = 206.0
|
||||||
@ -78,7 +78,7 @@ struct PasscodeKeyboardLayout {
|
|||||||
self.topOffset = 294.0
|
self.topOffset = 294.0
|
||||||
self.biometricsOffset = 30.0
|
self.biometricsOffset = 30.0
|
||||||
self.deleteOffset = 20.0
|
self.deleteOffset = 20.0
|
||||||
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax, .iPhone16ProMax:
|
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
|
||||||
self.buttonSize = 85.0
|
self.buttonSize = 85.0
|
||||||
self.horizontalSecond = 115.0
|
self.horizontalSecond = 115.0
|
||||||
self.horizontalThird = 230.0
|
self.horizontalThird = 230.0
|
||||||
@ -151,11 +151,11 @@ public struct PasscodeLayout {
|
|||||||
self.titleOffset = 112.0
|
self.titleOffset = 112.0
|
||||||
self.subtitleOffset = -6.0
|
self.subtitleOffset = -6.0
|
||||||
self.inputFieldOffset = 156.0
|
self.inputFieldOffset = 156.0
|
||||||
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed, .iPhone16Pro:
|
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
|
||||||
self.titleOffset = 162.0
|
self.titleOffset = 162.0
|
||||||
self.subtitleOffset = 0.0
|
self.subtitleOffset = 0.0
|
||||||
self.inputFieldOffset = 206.0
|
self.inputFieldOffset = 206.0
|
||||||
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax, .iPhone16ProMax:
|
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
|
||||||
self.titleOffset = 180.0
|
self.titleOffset = 180.0
|
||||||
self.subtitleOffset = 0.0
|
self.subtitleOffset = 0.0
|
||||||
self.inputFieldOffset = 226.0
|
self.inputFieldOffset = 226.0
|
||||||
|
|||||||
@ -2110,7 +2110,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||||
|
|
||||||
var reportSpeakingParticipants: [PeerId: UInt32] = [:]
|
var reportSpeakingParticipants: [PeerId: UInt32] = [:]
|
||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
for (peerId, ssrc) in speakingParticipants {
|
for (peerId, ssrc) in speakingParticipants {
|
||||||
let shouldReport: Bool
|
let shouldReport: Bool
|
||||||
if let previousTimestamp = strongSelf.speakingParticipantsReportTimestamp[peerId] {
|
if let previousTimestamp = strongSelf.speakingParticipantsReportTimestamp[peerId] {
|
||||||
|
|||||||
@ -109,6 +109,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
var applicationStateDisposable: Disposable?
|
var applicationStateDisposable: Disposable?
|
||||||
|
|
||||||
var expandedParticipantsVideoState: VideoChatParticipantsComponent.ExpandedVideoState?
|
var expandedParticipantsVideoState: VideoChatParticipantsComponent.ExpandedVideoState?
|
||||||
|
var focusedSpeakerAutoSwitchDeadline: Double = 0.0
|
||||||
var isTwoColumnSidebarHidden: Bool = false
|
var isTwoColumnSidebarHidden: Bool = false
|
||||||
|
|
||||||
let inviteDisposable = MetaDisposable()
|
let inviteDisposable = MetaDisposable()
|
||||||
@ -481,15 +482,6 @@ final class VideoChatScreenComponent: Component {
|
|||||||
guard let component = self.component, let environment = self.environment else {
|
guard let component = self.component, let environment = self.environment else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let callState = self.callState else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if case .connecting = callState.networkState {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let muteState = callState.muteState, !muteState.canUnmute {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
HapticFeedback().impact(.light)
|
HapticFeedback().impact(.light)
|
||||||
if component.call.hasVideo {
|
if component.call.hasVideo {
|
||||||
@ -761,7 +753,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
if self.members != members {
|
if self.members != members {
|
||||||
var members = members
|
var members = members
|
||||||
|
|
||||||
#if DEBUG && true
|
#if DEBUG && false
|
||||||
if let membersValue = members {
|
if let membersValue = members {
|
||||||
var participants = membersValue.participants
|
var participants = membersValue.participants
|
||||||
for i in 1 ... 20 {
|
for i in 1 ... 20 {
|
||||||
@ -840,12 +832,13 @@ final class VideoChatScreenComponent: Component {
|
|||||||
if videoCount == 1, let participantsView = self.participants.view as? VideoChatParticipantsComponent.View, let participantsComponent = participantsView.component {
|
if videoCount == 1, let participantsView = self.participants.view as? VideoChatParticipantsComponent.View, let participantsComponent = participantsView.component {
|
||||||
if participantsComponent.layout.videoColumn != nil {
|
if participantsComponent.layout.videoColumn != nil {
|
||||||
self.expandedParticipantsVideoState = nil
|
self.expandedParticipantsVideoState = nil
|
||||||
|
self.focusedSpeakerAutoSwitchDeadline = 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members {
|
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members {
|
||||||
if !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in
|
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in
|
||||||
if let callState = self.callState, participant.peer.id == callState.myPeerId {
|
if let callState = self.callState, participant.peer.id == callState.myPeerId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -862,6 +855,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
||||||
}
|
}
|
||||||
|
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,11 +888,14 @@ final class VideoChatScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden)
|
||||||
}
|
}
|
||||||
|
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0
|
||||||
} else {
|
} else {
|
||||||
self.expandedParticipantsVideoState = nil
|
self.expandedParticipantsVideoState = nil
|
||||||
|
self.focusedSpeakerAutoSwitchDeadline = 0.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.expandedParticipantsVideoState = nil
|
self.expandedParticipantsVideoState = nil
|
||||||
|
self.focusedSpeakerAutoSwitchDeadline = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
@ -1468,6 +1465,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: key, isMainParticipantPinned: false, isUIHidden: isUIHidden)
|
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: key, isMainParticipantPinned: false, isUIHidden: isUIHidden)
|
||||||
|
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 3.0
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
} else if self.expandedParticipantsVideoState != nil {
|
} else if self.expandedParticipantsVideoState != nil {
|
||||||
self.expandedParticipantsVideoState = nil
|
self.expandedParticipantsVideoState = nil
|
||||||
|
|||||||
@ -524,7 +524,7 @@ private func sendUploadedMessageContent(
|
|||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public func standaloneSendMessage(account: Account, peerId: PeerId, text: String, attributes: [MessageAttribute], media: StandaloneMedia?, replyToMessageId: MessageId?) -> Signal<Float, StandaloneSendMessageError> {
|
public func standaloneSendMessage(account: Account, peerId: PeerId, text: String, attributes: [MessageAttribute], media: StandaloneMedia?, replyToMessageId: MessageId?, threadId: Int32? = nil) -> Signal<Float, StandaloneSendMessageError> {
|
||||||
let content: Signal<StandaloneSendMessageEvent, StandaloneSendMessageError>
|
let content: Signal<StandaloneSendMessageEvent, StandaloneSendMessageError>
|
||||||
if let media = media {
|
if let media = media {
|
||||||
switch media {
|
switch media {
|
||||||
@ -561,14 +561,14 @@ public func standaloneSendMessage(account: Account, peerId: PeerId, text: String
|
|||||||
case let .progress(progress):
|
case let .progress(progress):
|
||||||
return .single(progress)
|
return .single(progress)
|
||||||
case let .result(result):
|
case let .result(result):
|
||||||
let sendContent = sendMessageContent(account: account, peerId: peerId, attributes: attributes, content: result) |> map({ _ -> Float in return 1.0 })
|
let sendContent = sendMessageContent(account: account, peerId: peerId, attributes: attributes, content: result, threadId: threadId) |> map({ _ -> Float in return 1.0 })
|
||||||
return .single(1.0) |> then(sendContent |> mapError { _ -> StandaloneSendMessageError in })
|
return .single(1.0) |> then(sendContent |> mapError { _ -> StandaloneSendMessageError in })
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendMessageContent(account: Account, peerId: PeerId, attributes: [MessageAttribute], content: StandaloneMessageContent) -> Signal<Void, NoError> {
|
private func sendMessageContent(account: Account, peerId: PeerId, attributes: [MessageAttribute], content: StandaloneMessageContent, threadId: Int32?) -> Signal<Void, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
return .complete()
|
return .complete()
|
||||||
@ -631,6 +631,9 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
|
|||||||
flags |= 1 << 0
|
flags |= 1 << 0
|
||||||
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
|
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
|
||||||
}
|
}
|
||||||
|
} else if let threadId {
|
||||||
|
flags |= 1 << 0
|
||||||
|
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil))
|
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil))
|
||||||
@ -649,6 +652,9 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
|
|||||||
flags |= 1 << 0
|
flags |= 1 << 0
|
||||||
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
|
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
|
||||||
}
|
}
|
||||||
|
} else if let threadId {
|
||||||
|
flags |= 1 << 0
|
||||||
|
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil))
|
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil))
|
||||||
|
|||||||
@ -731,6 +731,34 @@ public extension TelegramEngine.EngineData.Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct StarGiftsCount: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||||
|
public typealias Result = Int32?
|
||||||
|
|
||||||
|
fileprivate var id: EnginePeer.Id
|
||||||
|
public var mapKey: EnginePeer.Id {
|
||||||
|
return self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(id: EnginePeer.Id) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
var key: PostboxViewKey {
|
||||||
|
return .cachedPeerData(peerId: self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extract(view: PostboxView) -> Result {
|
||||||
|
guard let view = view as? CachedPeerDataView else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
if let cachedData = view.cachedPeerData as? CachedUserData {
|
||||||
|
return cachedData.starGiftsCount
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct LinkedDiscussionPeerId: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
public struct LinkedDiscussionPeerId: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||||
public typealias Result = EnginePeerCachedInfoItem<EnginePeer.Id?>
|
public typealias Result = EnginePeerCachedInfoItem<EnginePeer.Id?>
|
||||||
|
|
||||||
|
|||||||
@ -191,7 +191,7 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) ->
|
|||||||
|
|
||||||
func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
||||||
let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network)
|
let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network)
|
||||||
return (poll |> then(.complete() |> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_convertStarGift(account: Account, messageId: EngineMessage.Id) -> Signal<Never, NoError> {
|
func _internal_convertStarGift(account: Account, messageId: EngineMessage.Id) -> Signal<Never, NoError> {
|
||||||
|
|||||||
@ -228,7 +228,8 @@ private final class SheetPageContent: CombinedComponent {
|
|||||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.ReportAd_Help_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.ReportAd_Help_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
items: items
|
items: items,
|
||||||
|
isModal: true
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||||
|
|||||||
@ -30,6 +30,8 @@ swift_library(
|
|||||||
"//submodules/Markdown",
|
"//submodules/Markdown",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||||
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
|
"//submodules/InvisibleInkDustNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import ShimmerEffect
|
|||||||
import Markdown
|
import Markdown
|
||||||
import ChatMessageBubbleContentNode
|
import ChatMessageBubbleContentNode
|
||||||
import ChatMessageItemCommon
|
import ChatMessageItemCommon
|
||||||
|
import TextNodeWithEntities
|
||||||
|
import InvisibleInkDustNode
|
||||||
|
|
||||||
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? {
|
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? {
|
||||||
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false)
|
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false)
|
||||||
@ -34,7 +36,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
private let mediaBackgroundMaskNode: ASImageNode
|
private let mediaBackgroundMaskNode: ASImageNode
|
||||||
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
|
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let subtitleNode: TextNode
|
private let subtitleNode: TextNodeWithEntities
|
||||||
|
private var dustNode: InvisibleInkDustNode?
|
||||||
private let placeholderNode: StickerShimmerEffectNode
|
private let placeholderNode: StickerShimmerEffectNode
|
||||||
private let animationNode: AnimatedStickerNode
|
private let animationNode: AnimatedStickerNode
|
||||||
|
|
||||||
@ -60,6 +63,16 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
if wasVisible != isVisible {
|
if wasVisible != isVisible {
|
||||||
self.visibilityStatus = isVisible
|
self.visibilityStatus = isVisible
|
||||||
|
|
||||||
|
switch self.visibility {
|
||||||
|
case .none:
|
||||||
|
self.subtitleNode.visibilityRect = nil
|
||||||
|
case let .visible(_, subRect):
|
||||||
|
var subRect = subRect
|
||||||
|
subRect.origin.x = 0.0
|
||||||
|
subRect.size.width = 10000.0
|
||||||
|
self.subtitleNode.visibilityRect = subRect
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,9 +101,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.subtitleNode = TextNode()
|
self.subtitleNode = TextNodeWithEntities()
|
||||||
self.subtitleNode.isUserInteractionEnabled = false
|
self.subtitleNode.textNode.isUserInteractionEnabled = false
|
||||||
self.subtitleNode.displaysAsynchronously = false
|
self.subtitleNode.textNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
self.buttonNode.clipsToBounds = true
|
self.buttonNode.clipsToBounds = true
|
||||||
@ -120,8 +133,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.subtitleNode)
|
self.addSubnode(self.subtitleNode.textNode)
|
||||||
self.addSubnode(self.subtitleNode)
|
|
||||||
self.addSubnode(self.placeholderNode)
|
self.addSubnode(self.placeholderNode)
|
||||||
self.addSubnode(self.animationNode)
|
self.addSubnode(self.animationNode)
|
||||||
|
|
||||||
@ -236,7 +248,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
let makeSubtitleLayout = TextNodeWithEntities.asyncLayout(self.subtitleNode)
|
||||||
let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode)
|
let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode)
|
||||||
let makeRibbonTextLayout = TextNode.asyncLayout(self.ribbonTextNode)
|
let makeRibbonTextLayout = TextNode.asyncLayout(self.ribbonTextNode)
|
||||||
|
|
||||||
@ -259,6 +271,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var animationFile: TelegramMediaFile?
|
var animationFile: TelegramMediaFile?
|
||||||
var title = item.presentationData.strings.Notification_PremiumGift_Title
|
var title = item.presentationData.strings.Notification_PremiumGift_Title
|
||||||
var text = ""
|
var text = ""
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View
|
var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View
|
||||||
var ribbonTitle = ""
|
var ribbonTitle = ""
|
||||||
var hasServiceMessage = true
|
var hasServiceMessage = true
|
||||||
@ -329,14 +342,16 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
|
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
|
||||||
hasServiceMessage = false
|
hasServiceMessage = false
|
||||||
}
|
}
|
||||||
case let .starGift(gift, convertStars, giftText, entities, nameHidden, savedToProfile, converted):
|
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted):
|
||||||
let _ = nameHidden
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
|
if !incoming {
|
||||||
|
buttonTitle = ""
|
||||||
|
}
|
||||||
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
|
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
|
||||||
title = "Gift from \(authorName)"
|
title = "Gift from \(authorName)"
|
||||||
if let giftText, !giftText.isEmpty {
|
if let giftText, !giftText.isEmpty {
|
||||||
text = giftText
|
text = giftText
|
||||||
let _ = entities
|
entities = giftEntities ?? []
|
||||||
} else {
|
} else {
|
||||||
if incoming {
|
if incoming {
|
||||||
if converted {
|
if converted {
|
||||||
@ -383,7 +398,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
|
let attributedText: NSAttributedString
|
||||||
|
if let _ = animationFile {
|
||||||
|
attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.monospace(13.0), blockQuoteFont: Font.regular(13.0), message: nil)
|
||||||
|
} else {
|
||||||
|
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
|
||||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor),
|
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor),
|
||||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor),
|
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor),
|
||||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor),
|
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor),
|
||||||
@ -391,6 +410,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return ("URL", url)
|
return ("URL", url)
|
||||||
}
|
}
|
||||||
), textAlignment: .center)
|
), textAlignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
@ -398,7 +418,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
giftSize.height = titleLayout.size.height + textSpacing + subtitleLayout.size.height + 212.0
|
giftSize.height = titleLayout.size.height + textSpacing + subtitleLayout.size.height + 164.0
|
||||||
|
if !buttonTitle.isEmpty {
|
||||||
|
giftSize.height += 48.0
|
||||||
|
}
|
||||||
|
|
||||||
var labelRects = labelLayout.linesRects()
|
var labelRects = labelLayout.linesRects()
|
||||||
if labelRects.count > 1 {
|
if labelRects.count > 1 {
|
||||||
@ -458,6 +481,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize)
|
let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize)
|
||||||
strongSelf.animationNode.frame = animationFrame
|
strongSelf.animationNode.frame = animationFrame
|
||||||
|
|
||||||
|
strongSelf.buttonNode.isHidden = buttonTitle.isEmpty
|
||||||
|
strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty
|
||||||
|
|
||||||
if strongSelf.item == nil {
|
if strongSelf.item == nil {
|
||||||
strongSelf.animationNode.started = { [weak self] in
|
strongSelf.animationNode.started = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -502,7 +528,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let _ = labelApply()
|
let _ = labelApply()
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = subtitleApply()
|
let _ = subtitleApply(TextNodeWithEntities.Arguments(
|
||||||
|
context: item.context,
|
||||||
|
cache: item.controllerInteraction.presentationContext.animationCache,
|
||||||
|
renderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||||
|
placeholderColor: item.presentationData.theme.theme.chat.message.freeform.withWallpaper.reactionInactiveBackground,
|
||||||
|
attemptSynchronous: synchronousLoads
|
||||||
|
))
|
||||||
let _ = buttonTitleApply()
|
let _ = buttonTitleApply()
|
||||||
let _ = ribbonTextApply()
|
let _ = ribbonTextApply()
|
||||||
|
|
||||||
@ -513,7 +545,26 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.titleNode.frame = titleFrame
|
strongSelf.titleNode.frame = titleFrame
|
||||||
|
|
||||||
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: subtitleLayout.size)
|
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: subtitleLayout.size)
|
||||||
strongSelf.subtitleNode.frame = subtitleFrame
|
strongSelf.subtitleNode.textNode.frame = subtitleFrame
|
||||||
|
|
||||||
|
if !subtitleLayout.spoilers.isEmpty {
|
||||||
|
let dustColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText
|
||||||
|
|
||||||
|
let dustNode: InvisibleInkDustNode
|
||||||
|
if let current = strongSelf.dustNode {
|
||||||
|
dustNode = current
|
||||||
|
} else {
|
||||||
|
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||||
|
dustNode.isUserInteractionEnabled = false
|
||||||
|
strongSelf.dustNode = dustNode
|
||||||
|
strongSelf.insertSubnode(dustNode, aboveSubnode: strongSelf.subtitleNode.textNode)
|
||||||
|
}
|
||||||
|
dustNode.frame = subtitleFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 1.0)
|
||||||
|
dustNode.update(size: dustNode.frame.size, color: dustColor, textColor: dustColor, rects: subtitleLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: subtitleLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||||
|
} else if let dustNode = strongSelf.dustNode {
|
||||||
|
dustNode.removeFromSupernode()
|
||||||
|
strongSelf.dustNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size)
|
let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size)
|
||||||
strongSelf.buttonTitleNode.frame = buttonTitleFrame
|
strongSelf.buttonTitleNode.frame = buttonTitleFrame
|
||||||
@ -607,6 +658,16 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let (rect, size) = strongSelf.absoluteRect {
|
if let (rect, size) = strongSelf.absoluteRect {
|
||||||
strongSelf.updateAbsoluteRect(rect, within: size)
|
strongSelf.updateAbsoluteRect(rect, within: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch strongSelf.visibility {
|
||||||
|
case .none:
|
||||||
|
strongSelf.subtitleNode.visibilityRect = nil
|
||||||
|
case let .visible(_, subRect):
|
||||||
|
var subRect = subRect
|
||||||
|
subRect.origin.x = 0.0
|
||||||
|
subRect.size.width = 10000.0
|
||||||
|
strongSelf.subtitleNode.visibilityRect = subRect
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -733,6 +794,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var internalPlayedOnce = false
|
||||||
private func updateVisibility() {
|
private func updateVisibility() {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
@ -763,9 +825,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) && !self.internalPlayedOnce {
|
||||||
item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id)
|
item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id)
|
||||||
self.animationNode.playOnce()
|
self.animationNode.playOnce()
|
||||||
|
self.internalPlayedOnce = true
|
||||||
|
|
||||||
Queue.mainQueue().after(0.05) {
|
Queue.mainQueue().after(0.05) {
|
||||||
if let itemNode = self.itemNode, let supernode = itemNode.supernode {
|
if let itemNode = self.itemNode, let supernode = itemNode.supernode {
|
||||||
|
|||||||
@ -162,7 +162,7 @@ private final class SheetPageContent: CombinedComponent {
|
|||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(back
|
context.add(back
|
||||||
.position(CGPoint(x: sideInset + back.size.width / 2.0 - (component.title != nil ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0))
|
.position(CGPoint(x: sideInset + back.size.width / 2.0 - (!component.isFirst ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
let constrainedTitleWidth = context.availableSize.width - (back.size.width + 16.0) * 2.0
|
let constrainedTitleWidth = context.availableSize.width - (back.size.width + 16.0) * 2.0
|
||||||
@ -280,7 +280,8 @@ private final class SheetPageContent: CombinedComponent {
|
|||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)),
|
)),
|
||||||
footer: footer,
|
footer: footer,
|
||||||
items: items
|
items: items,
|
||||||
|
isModal: true
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||||
@ -696,12 +697,10 @@ public final class ContentReportScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .reported:
|
case .reported:
|
||||||
Queue.mainQueue().after(0.1) {
|
|
||||||
completed()
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
Queue.mainQueue().after(0.4, {
|
Queue.mainQueue().after(0.4, {
|
||||||
|
completed()
|
||||||
|
|
||||||
(navigationController?.viewControllers.last as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(name: "PoliceCar", text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return true }), in: .current)
|
(navigationController?.viewControllers.last as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(name: "PoliceCar", text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1011,14 +1011,15 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScreenProtocol {
|
open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScreenProtocol {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
starsContext: StarsContext,
|
starsContext: StarsContext,
|
||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
premiumOptions: [CachedPremiumGiftOption]
|
premiumOptions: [CachedPremiumGiftOption],
|
||||||
|
completion: @escaping () -> Void = {}
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
|||||||
@ -34,10 +34,13 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
"//submodules/AppBundle",
|
"//submodules/AppBundle",
|
||||||
"//submodules/WallpaperBackgroundNode",
|
"//submodules/WallpaperBackgroundNode",
|
||||||
|
"//submodules/TextFormat",
|
||||||
"//submodules/ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
|
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
|
||||||
"//submodules/BotPaymentsUI",
|
"//submodules/BotPaymentsUI",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -28,6 +28,7 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
|
|||||||
let accountPeer: EnginePeer?
|
let accountPeer: EnginePeer?
|
||||||
let gift: StarGift
|
let gift: StarGift
|
||||||
let text: String
|
let text: String
|
||||||
|
let entities: [MessageTextEntity]
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -42,7 +43,8 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
|
|||||||
nameDisplayOrder: PresentationPersonNameOrder,
|
nameDisplayOrder: PresentationPersonNameOrder,
|
||||||
accountPeer: EnginePeer?,
|
accountPeer: EnginePeer?,
|
||||||
gift: StarGift,
|
gift: StarGift,
|
||||||
text: String
|
text: String,
|
||||||
|
entities: [MessageTextEntity]
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -57,6 +59,7 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
|
|||||||
self.accountPeer = accountPeer
|
self.accountPeer = accountPeer
|
||||||
self.gift = gift
|
self.gift = gift
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.entities = entities
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
@ -130,6 +133,9 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
|
|||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.entities != rhs.entities {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +207,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||||||
peers[authorPeerId] = item.accountPeer?._asPeer()
|
peers[authorPeerId] = item.accountPeer?._asPeer()
|
||||||
|
|
||||||
let media: [Media] = [
|
let media: [Media] = [
|
||||||
TelegramMediaAction(action: .starGift(gift: item.gift, convertStars: item.gift.convertStars, text: item.text, entities: [], nameHidden: false, savedToProfile: false, converted: false))
|
TelegramMediaAction(action: .starGift(gift: item.gift, convertStars: item.gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false))
|
||||||
]
|
]
|
||||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
|
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
|
||||||
@ -221,21 +227,21 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||||||
itemNode.insets = layout.insets
|
itemNode.insets = layout.insets
|
||||||
itemNode.frame = nodeFrame
|
itemNode.frame = nodeFrame
|
||||||
itemNode.isUserInteractionEnabled = false
|
itemNode.isUserInteractionEnabled = false
|
||||||
|
itemNode.visibility = .visible(1.0, .infinite)
|
||||||
|
|
||||||
Queue.mainQueue().after(0.01) {
|
|
||||||
apply(ListViewItemApply(isOnScreen: true))
|
apply(ListViewItemApply(isOnScreen: true))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var messageNodes: [ListViewItemNode] = []
|
var messageNodes: [ListViewItemNode] = []
|
||||||
for i in 0 ..< items.count {
|
for i in 0 ..< items.count {
|
||||||
var itemNode: ListViewItemNode?
|
var itemNode: ListViewItemNode?
|
||||||
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: true, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
||||||
itemNode = node
|
itemNode = node
|
||||||
apply().1(ListViewItemApply(isOnScreen: true))
|
apply().1(ListViewItemApply(isOnScreen: true))
|
||||||
})
|
})
|
||||||
itemNode!.isUserInteractionEnabled = false
|
itemNode!.isUserInteractionEnabled = false
|
||||||
|
itemNode?.visibility = .visible(1.0, .infinite)
|
||||||
messageNodes.append(itemNode!)
|
messageNodes.append(itemNode!)
|
||||||
|
|
||||||
self.initialBubbleHeight = itemNode?.frame.height
|
self.initialBubbleHeight = itemNode?.frame.height
|
||||||
|
|||||||
@ -22,6 +22,11 @@ import LottieComponent
|
|||||||
import TextFieldComponent
|
import TextFieldComponent
|
||||||
import ButtonComponent
|
import ButtonComponent
|
||||||
import BotPaymentsUI
|
import BotPaymentsUI
|
||||||
|
import ChatEntityKeyboardInputNode
|
||||||
|
import EmojiSuggestionsComponent
|
||||||
|
import ChatPresentationInterfaceState
|
||||||
|
import AudioToolbox
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
final class GiftSetupScreenComponent: Component {
|
final class GiftSetupScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
@ -81,9 +86,23 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
private let textInputTag = NSObject()
|
private let textInputTag = NSObject()
|
||||||
private var resetText: String?
|
private var resetText: String?
|
||||||
|
|
||||||
|
private var currentInputMode: ListMultilineTextFieldItemComponent.InputMode = .keyboard
|
||||||
|
|
||||||
|
private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
|
||||||
|
private var inputMediaNodeDataDisposable: Disposable?
|
||||||
|
private var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext()
|
||||||
|
private var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction?
|
||||||
|
private var inputMediaNode: ChatEntityKeyboardInputNode?
|
||||||
|
private var inputMediaNodeBackground = SimpleLayer()
|
||||||
|
private var inputMediaNodeTargetTag: AnyObject?
|
||||||
|
private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
|
||||||
|
|
||||||
|
private var currentEmojiSuggestionView: ComponentHostView<Empty>?
|
||||||
|
|
||||||
private var hideName = false
|
private var hideName = false
|
||||||
|
|
||||||
private var previousHadInputHeight: Bool = false
|
private var previousHadInputHeight: Bool = false
|
||||||
|
private var previousInputHeight: CGFloat?
|
||||||
private var recenterOnTag: NSObject?
|
private var recenterOnTag: NSObject?
|
||||||
|
|
||||||
private var peerMap: [EnginePeer.Id: EnginePeer] = [:]
|
private var peerMap: [EnginePeer.Id: EnginePeer] = [:]
|
||||||
@ -175,7 +194,8 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: component.gift.id, text: self.textInputState.text.string, entities: [])
|
let entities = generateChatInputTextEntities(self.textInputState.text)
|
||||||
|
let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: component.gift.id, text: self.textInputState.text.string, entities: entities)
|
||||||
let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|
let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
|
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
|
||||||
@ -264,6 +284,108 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
|
|
||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.inputMediaNodeDataPromise.set(
|
||||||
|
ChatEntityKeyboardInputNode.inputData(
|
||||||
|
context: component.context,
|
||||||
|
chatPeerId: nil,
|
||||||
|
areCustomEmojiEnabled: true,
|
||||||
|
hasTrending: false,
|
||||||
|
hasSearch: true,
|
||||||
|
hasStickers: false,
|
||||||
|
hasGifs: false,
|
||||||
|
hideBackground: true,
|
||||||
|
sendGif: nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.inputMediaNodeData = value
|
||||||
|
})
|
||||||
|
|
||||||
|
self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction(
|
||||||
|
sendSticker: { _, _, _, _, _, _, _, _, _ in
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
sendEmoji: { _, _, _ in
|
||||||
|
let _ = self
|
||||||
|
},
|
||||||
|
sendGif: { _, _, _, _, _ in
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
sendBotContextResultAsGif: { _, _ , _, _, _, _ in
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
updateChoosingSticker: { _ in
|
||||||
|
},
|
||||||
|
switchToTextInput: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.currentInputMode = .keyboard
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
},
|
||||||
|
dismissTextInput: {
|
||||||
|
},
|
||||||
|
insertText: { [weak self] text in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let textInputView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View {
|
||||||
|
if self.textInputState.isEditing {
|
||||||
|
textInputView.insertText(text: text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backwardsDeleteText: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let textInputView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View {
|
||||||
|
if self.textInputState.isEditing {
|
||||||
|
textInputView.backwardsDeleteText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openStickerEditor: {
|
||||||
|
},
|
||||||
|
presentController: { [weak self] c, a in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.environment?.controller()?.present(c, in: .window(.root), with: a)
|
||||||
|
},
|
||||||
|
presentGlobalOverlayController: { [weak self] c, a in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.environment?.controller()?.presentInGlobalOverlay(c, with: a)
|
||||||
|
},
|
||||||
|
getNavigationController: { [weak self] () -> NavigationController? in
|
||||||
|
guard let self else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let controller = self.environment?.controller() as? GiftSetupScreen else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
|
return navigationController
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
requestLayout: { [weak self] transition in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: ComponentTransition(transition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let environment = environment[EnvironmentType.self].value
|
let environment = environment[EnvironmentType.self].value
|
||||||
@ -317,15 +439,6 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
contentHeight += environment.navigationHeight
|
contentHeight += environment.navigationHeight
|
||||||
contentHeight += 26.0
|
contentHeight += 26.0
|
||||||
|
|
||||||
self.recenterOnTag = nil
|
|
||||||
if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let targetView = hint.view {
|
|
||||||
if let textView = self.introSection.findTaggedView(tag: self.textInputTag) {
|
|
||||||
if targetView.isDescendant(of: textView) {
|
|
||||||
self.recenterOnTag = self.textInputTag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||||
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
|
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
|
||||||
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
|
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
|
||||||
@ -337,13 +450,14 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
resetText: self.resetText.flatMap {
|
resetText: self.resetText.flatMap {
|
||||||
return ListMultilineTextFieldItemComponent.ResetText(value: $0)
|
return ListMultilineTextFieldItemComponent.ResetText(value: $0)
|
||||||
},
|
},
|
||||||
placeholder: environment.strings.Business_Intro_IntroTextPlaceholder,
|
placeholder: "Enter Message",
|
||||||
autocapitalizationType: .none,
|
autocapitalizationType: .none,
|
||||||
autocorrectionType: .no,
|
autocorrectionType: .no,
|
||||||
returnKeyType: .done,
|
returnKeyType: .done,
|
||||||
characterLimit: 70,
|
characterLimit: 255,
|
||||||
displayCharacterLimit: true,
|
displayCharacterLimit: true,
|
||||||
emptyLineHandling: .notAllowed,
|
emptyLineHandling: .notAllowed,
|
||||||
|
formatMenuAvailability: .available([.bold, .italic, .underline, .strikethrough, .spoiler]),
|
||||||
updated: { _ in
|
updated: { _ in
|
||||||
},
|
},
|
||||||
returnKeyAction: { [weak self] in
|
returnKeyAction: { [weak self] in
|
||||||
@ -355,10 +469,173 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
textUpdateTransition: .spring(duration: 0.4),
|
textUpdateTransition: .spring(duration: 0.4),
|
||||||
|
inputMode: self.currentInputMode,
|
||||||
|
toggleInputMode: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch self.currentInputMode {
|
||||||
|
case .keyboard:
|
||||||
|
self.currentInputMode = .emoji
|
||||||
|
case .emoji:
|
||||||
|
self.currentInputMode = .keyboard
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
},
|
||||||
tag: self.textInputTag
|
tag: self.textInputTag
|
||||||
))))
|
))))
|
||||||
self.resetText = nil
|
self.resetText = nil
|
||||||
|
|
||||||
|
|
||||||
|
var inputHeight: CGFloat = 0.0
|
||||||
|
inputHeight += self.updateInputMediaNode(
|
||||||
|
component: component,
|
||||||
|
availableSize: availableSize,
|
||||||
|
bottomInset: environment.safeInsets.bottom,
|
||||||
|
inputHeight: 0.0,
|
||||||
|
effectiveInputHeight: environment.deviceMetrics.standardInputHeight(inLandscape: false),
|
||||||
|
metrics: environment.metrics,
|
||||||
|
deviceMetrics: environment.deviceMetrics,
|
||||||
|
transition: transition
|
||||||
|
)
|
||||||
|
if self.inputMediaNode == nil {
|
||||||
|
if environment.inputHeight.isZero && self.textInputState.isEditing, let previousInputHeight = self.previousInputHeight {
|
||||||
|
inputHeight = previousInputHeight
|
||||||
|
} else {
|
||||||
|
inputHeight = environment.inputHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.textInputState.isEditing, let emojiSuggestion = self.textInputState.currentEmojiSuggestion, emojiSuggestion.disposable == nil {
|
||||||
|
emojiSuggestion.disposable = (EmojiSuggestionsComponent.suggestionData(context: component.context, isSavedMessages: false, query: emojiSuggestion.position.value)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak emojiSuggestion] result in
|
||||||
|
guard let self, self.textInputState.currentEmojiSuggestion === emojiSuggestion else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emojiSuggestion?.value = result
|
||||||
|
self.state?.updated()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasTrackingView = self.textInputState.hasTrackingView
|
||||||
|
if let currentEmojiSuggestion = self.textInputState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile], value.isEmpty {
|
||||||
|
hasTrackingView = false
|
||||||
|
}
|
||||||
|
if !self.textInputState.isEditing {
|
||||||
|
hasTrackingView = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasTrackingView {
|
||||||
|
if let currentEmojiSuggestion = self.textInputState.currentEmojiSuggestion {
|
||||||
|
self.textInputState.currentEmojiSuggestion = nil
|
||||||
|
currentEmojiSuggestion.disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentEmojiSuggestionView = self.currentEmojiSuggestionView {
|
||||||
|
self.currentEmojiSuggestionView = nil
|
||||||
|
|
||||||
|
currentEmojiSuggestionView.alpha = 0.0
|
||||||
|
currentEmojiSuggestionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak currentEmojiSuggestionView] _ in
|
||||||
|
currentEmojiSuggestionView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.textInputState.isEditing, let emojiSuggestion = self.textInputState.currentEmojiSuggestion, let value = emojiSuggestion.value as? [TelegramMediaFile] {
|
||||||
|
let currentEmojiSuggestionView: ComponentHostView<Empty>
|
||||||
|
if let current = self.currentEmojiSuggestionView {
|
||||||
|
currentEmojiSuggestionView = current
|
||||||
|
} else {
|
||||||
|
currentEmojiSuggestionView = ComponentHostView<Empty>()
|
||||||
|
self.currentEmojiSuggestionView = currentEmojiSuggestionView
|
||||||
|
self.addSubview(currentEmojiSuggestionView)
|
||||||
|
|
||||||
|
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
let globalPosition: CGPoint
|
||||||
|
if let textView = (self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View)?.textFieldView {
|
||||||
|
globalPosition = textView.convert(emojiSuggestion.localPosition, to: self)
|
||||||
|
} else {
|
||||||
|
globalPosition = .zero
|
||||||
|
}
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 7.0
|
||||||
|
|
||||||
|
let viewSize = currentEmojiSuggestionView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(EmojiSuggestionsComponent(
|
||||||
|
context: component.context,
|
||||||
|
userLocation: .other,
|
||||||
|
theme: EmojiSuggestionsComponent.Theme(theme: environment.theme),
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
animationRenderer: component.context.animationRenderer,
|
||||||
|
files: value,
|
||||||
|
action: { [weak self] file in
|
||||||
|
guard let self, let textView = (self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View)?.textFieldView, let currentEmojiSuggestion = self.textInputState.currentEmojiSuggestion else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioServicesPlaySystemSound(0x450)
|
||||||
|
|
||||||
|
let inputState = textView.getInputState()
|
||||||
|
let inputText = NSMutableAttributedString(attributedString: inputState.inputText)
|
||||||
|
|
||||||
|
var text: String?
|
||||||
|
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
||||||
|
loop: for attribute in file.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .CustomEmoji(_, _, displayText, _):
|
||||||
|
text = displayText
|
||||||
|
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file)
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emojiAttribute = emojiAttribute, let text = text {
|
||||||
|
let replacementText = NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])
|
||||||
|
|
||||||
|
let range = currentEmojiSuggestion.position.range
|
||||||
|
let previousText = inputText.attributedSubstring(from: range)
|
||||||
|
inputText.replaceCharacters(in: range, with: replacementText)
|
||||||
|
|
||||||
|
var replacedUpperBound = range.lowerBound
|
||||||
|
while true {
|
||||||
|
if inputText.attributedSubstring(from: NSRange(location: 0, length: replacedUpperBound)).string.hasSuffix(previousText.string) {
|
||||||
|
let replaceRange = NSRange(location: replacedUpperBound - previousText.length, length: previousText.length)
|
||||||
|
if replaceRange.location < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let adjacentString = inputText.attributedSubstring(from: replaceRange)
|
||||||
|
if adjacentString.string != previousText.string || adjacentString.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
inputText.replaceCharacters(in: replaceRange, with: NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: emojiAttribute.interactivelySelectedFromPackId, fileId: emojiAttribute.fileId, file: emojiAttribute.file)]))
|
||||||
|
replacedUpperBound = replaceRange.lowerBound
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectionPosition = range.lowerBound + (replacementText.string as NSString).length
|
||||||
|
textView.updateText(inputText, selectionRange: selectionPosition ..< selectionPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let viewFrame = CGRect(origin: CGPoint(x: min(availableSize.width - sideInset - viewSize.width, max(sideInset, floor(globalPosition.x - viewSize.width / 2.0))), y: globalPosition.y - 4.0 - viewSize.height), size: viewSize)
|
||||||
|
currentEmojiSuggestionView.frame = viewFrame
|
||||||
|
if let componentView = currentEmojiSuggestionView.componentView as? EmojiSuggestionsComponent.View {
|
||||||
|
componentView.adjustBackground(relativePositionX: floor(globalPosition.x + 10.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let introSectionSize = self.introSection.update(
|
let introSectionSize = self.introSection.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ListSectionComponent(
|
component: AnyComponent(ListSectionComponent(
|
||||||
@ -388,23 +665,8 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
contentHeight += introSectionSize.height
|
contentHeight += introSectionSize.height
|
||||||
contentHeight += sectionSpacing
|
contentHeight += sectionSpacing
|
||||||
|
|
||||||
// let titleText: String
|
|
||||||
// if self.titleInputState.text.string.isEmpty {
|
|
||||||
// titleText = environment.strings.Conversation_EmptyPlaceholder
|
|
||||||
// } else {
|
|
||||||
// let rawTitle = self.titleInputState.text.string
|
|
||||||
// titleText = rawTitle.count <= maxTitleLength ? rawTitle : String(rawTitle[rawTitle.startIndex ..< rawTitle.index(rawTitle.startIndex, offsetBy: maxTitleLength)])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let textText: String
|
|
||||||
// if self.textInputState.text.string.isEmpty {
|
|
||||||
// textText = environment.strings.Conversation_GreetingText
|
|
||||||
// } else {
|
|
||||||
// let rawText = self.textInputState.text.string
|
|
||||||
// textText = rawText.count <= maxTextLength ? rawText : String(rawText[rawText.startIndex ..< rawText.index(rawText.startIndex, offsetBy: maxTextLength)])
|
|
||||||
// }
|
|
||||||
|
|
||||||
let listItemParams = ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
let listItemParams = ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
||||||
|
if let accountPeer = self.peerMap[component.context.account.peerId] {
|
||||||
let introContentSize = self.introContent.update(
|
let introContentSize = self.introContent.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
@ -420,9 +682,10 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
wallpaper: presentationData.chatWallpaper,
|
wallpaper: presentationData.chatWallpaper,
|
||||||
dateTimeFormat: environment.dateTimeFormat,
|
dateTimeFormat: environment.dateTimeFormat,
|
||||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||||
accountPeer: self.peerMap[component.context.account.peerId],
|
accountPeer: accountPeer,
|
||||||
gift: component.gift,
|
gift: component.gift,
|
||||||
text: self.textInputState.text.string
|
text: self.textInputState.text.string,
|
||||||
|
entities: generateChatInputTextEntities(self.textInputState.text)
|
||||||
),
|
),
|
||||||
params: listItemParams
|
params: listItemParams
|
||||||
)
|
)
|
||||||
@ -438,13 +701,7 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize))
|
transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.recenterOnTag == nil && self.previousHadInputHeight != (environment.inputHeight > 0.0) {
|
|
||||||
if self.textInputState.isEditing {
|
|
||||||
self.recenterOnTag = self.textInputTag
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.previousHadInputHeight = environment.inputHeight > 0.0
|
|
||||||
|
|
||||||
let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? ""
|
let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? ""
|
||||||
let hideSectionSize = self.hideSection.update(
|
let hideSectionSize = self.hideSection.update(
|
||||||
@ -498,11 +755,9 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
|
|
||||||
contentHeight += bottomContentInset
|
contentHeight += bottomContentInset
|
||||||
|
|
||||||
let inputHeight: CGFloat = environment.inputHeight
|
|
||||||
let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom)
|
let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom)
|
||||||
contentHeight += combinedBottomInset
|
contentHeight += combinedBottomInset
|
||||||
|
|
||||||
|
|
||||||
if self.starImage == nil || self.starImage?.1 !== environment.theme {
|
if self.starImage == nil || self.starImage?.1 !== environment.theme {
|
||||||
self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme)
|
self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme)
|
||||||
}
|
}
|
||||||
@ -545,6 +800,22 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
|
|
||||||
let previousBounds = self.scrollView.bounds
|
let previousBounds = self.scrollView.bounds
|
||||||
|
|
||||||
|
self.recenterOnTag = nil
|
||||||
|
if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let targetView = hint.view {
|
||||||
|
if let textView = self.introSection.findTaggedView(tag: self.textInputTag) {
|
||||||
|
if targetView.isDescendant(of: textView) {
|
||||||
|
self.recenterOnTag = self.textInputTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.recenterOnTag == nil && self.previousHadInputHeight != (environment.inputHeight > 0.0) {
|
||||||
|
if self.textInputState.isEditing {
|
||||||
|
self.recenterOnTag = self.textInputTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.previousHadInputHeight = inputHeight > 0.0
|
||||||
|
self.previousInputHeight = inputHeight
|
||||||
|
|
||||||
self.ignoreScrolling = true
|
self.ignoreScrolling = true
|
||||||
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||||
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
|
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
|
||||||
@ -592,6 +863,152 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateInputMediaNode(
|
||||||
|
component: GiftSetupScreenComponent,
|
||||||
|
availableSize: CGSize,
|
||||||
|
bottomInset: CGFloat,
|
||||||
|
inputHeight: CGFloat,
|
||||||
|
effectiveInputHeight: CGFloat,
|
||||||
|
metrics: LayoutMetrics,
|
||||||
|
deviceMetrics: DeviceMetrics,
|
||||||
|
transition: ComponentTransition
|
||||||
|
) -> CGFloat {
|
||||||
|
let bottomInset: CGFloat = bottomInset + 8.0
|
||||||
|
let bottomContainerInset: CGFloat = 0.0
|
||||||
|
let needsInputActivation: Bool = !"".isEmpty
|
||||||
|
|
||||||
|
var height: CGFloat = 0.0
|
||||||
|
if case .emoji = self.currentInputMode, let inputData = self.inputMediaNodeData {
|
||||||
|
let inputMediaNode: ChatEntityKeyboardInputNode
|
||||||
|
var inputMediaNodeTransition = transition
|
||||||
|
var animateIn = false
|
||||||
|
if let current = self.inputMediaNode {
|
||||||
|
inputMediaNode = current
|
||||||
|
} else {
|
||||||
|
animateIn = true
|
||||||
|
inputMediaNodeTransition = inputMediaNodeTransition.withAnimation(.none)
|
||||||
|
inputMediaNode = ChatEntityKeyboardInputNode(
|
||||||
|
context: component.context,
|
||||||
|
currentInputData: inputData,
|
||||||
|
updatedInputData: self.inputMediaNodeDataPromise.get(),
|
||||||
|
defaultToEmojiTab: true,
|
||||||
|
opaqueTopPanelBackground: false,
|
||||||
|
useOpaqueTheme: true,
|
||||||
|
interaction: self.inputMediaInteraction,
|
||||||
|
chatPeerId: nil,
|
||||||
|
stateContext: self.inputMediaNodeStateContext
|
||||||
|
)
|
||||||
|
inputMediaNode.clipsToBounds = true
|
||||||
|
|
||||||
|
inputMediaNode.externalTopPanelContainerImpl = nil
|
||||||
|
inputMediaNode.useExternalSearchContainer = true
|
||||||
|
if inputMediaNode.view.superview == nil {
|
||||||
|
self.inputMediaNodeBackground.removeAllAnimations()
|
||||||
|
self.layer.addSublayer(self.inputMediaNodeBackground)
|
||||||
|
self.addSubview(inputMediaNode.view)
|
||||||
|
}
|
||||||
|
self.inputMediaNode = inputMediaNode
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let presentationInterfaceState = ChatPresentationInterfaceState(
|
||||||
|
chatWallpaper: .builtin(WallpaperSettings()),
|
||||||
|
theme: presentationData.theme,
|
||||||
|
strings: presentationData.strings,
|
||||||
|
dateTimeFormat: presentationData.dateTimeFormat,
|
||||||
|
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||||
|
limitsConfiguration: component.context.currentLimitsConfiguration.with { $0 },
|
||||||
|
fontSize: presentationData.chatFontSize,
|
||||||
|
bubbleCorners: presentationData.chatBubbleCorners,
|
||||||
|
accountPeerId: component.context.account.peerId,
|
||||||
|
mode: .standard(.default),
|
||||||
|
chatLocation: .peer(id: component.context.account.peerId),
|
||||||
|
subject: nil,
|
||||||
|
peerNearbyData: nil,
|
||||||
|
greetingData: nil,
|
||||||
|
pendingUnpinnedAllMessages: false,
|
||||||
|
activeGroupCallInfo: nil,
|
||||||
|
hasActiveGroupCall: false,
|
||||||
|
importState: nil,
|
||||||
|
threadData: nil,
|
||||||
|
isGeneralThreadClosed: nil,
|
||||||
|
replyMessage: nil,
|
||||||
|
accountPeerColor: nil,
|
||||||
|
businessIntro: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
self.inputMediaNodeBackground.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor
|
||||||
|
|
||||||
|
let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight < 100.0 ? inputHeight - bottomContainerInset : inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false)
|
||||||
|
let inputNodeHeight = heightAndOverflow.0
|
||||||
|
let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight))
|
||||||
|
|
||||||
|
let inputNodeBackgroundFrame = CGRect(origin: CGPoint(x: inputNodeFrame.minX, y: inputNodeFrame.minY - 6.0), size: CGSize(width: inputNodeFrame.width, height: inputNodeFrame.height + 6.0))
|
||||||
|
|
||||||
|
if needsInputActivation {
|
||||||
|
let inputNodeFrame = inputNodeFrame.offsetBy(dx: 0.0, dy: inputNodeHeight)
|
||||||
|
ComponentTransition.immediate.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
|
||||||
|
ComponentTransition.immediate.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
if animateIn {
|
||||||
|
var targetFrame = inputNodeFrame
|
||||||
|
targetFrame.origin.y = availableSize.height
|
||||||
|
inputMediaNodeTransition.setFrame(layer: inputMediaNode.layer, frame: targetFrame)
|
||||||
|
|
||||||
|
let inputNodeBackgroundTargetFrame = CGRect(origin: CGPoint(x: targetFrame.minX, y: targetFrame.minY - 6.0), size: CGSize(width: targetFrame.width, height: targetFrame.height + 6.0))
|
||||||
|
|
||||||
|
inputMediaNodeTransition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundTargetFrame)
|
||||||
|
|
||||||
|
transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
|
||||||
|
transition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundFrame)
|
||||||
|
} else {
|
||||||
|
inputMediaNodeTransition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
|
||||||
|
inputMediaNodeTransition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
height = heightAndOverflow.0
|
||||||
|
} else {
|
||||||
|
self.inputMediaNodeTargetTag = nil
|
||||||
|
|
||||||
|
if let inputMediaNode = self.inputMediaNode {
|
||||||
|
self.inputMediaNode = nil
|
||||||
|
var targetFrame = inputMediaNode.frame
|
||||||
|
targetFrame.origin.y = availableSize.height
|
||||||
|
transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in
|
||||||
|
if let inputMediaNode {
|
||||||
|
Queue.mainQueue().after(0.3) {
|
||||||
|
inputMediaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in
|
||||||
|
inputMediaNode?.view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
transition.setFrame(layer: self.inputMediaNodeBackground, frame: targetFrame, completion: { [weak self] _ in
|
||||||
|
Queue.mainQueue().after(0.3) {
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.currentInputMode == .keyboard {
|
||||||
|
self.inputMediaNodeBackground.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak self] finished in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if finished {
|
||||||
|
self.inputMediaNodeBackground.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
self.inputMediaNodeBackground.removeAllAnimations()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeView() -> View {
|
func makeView() -> View {
|
||||||
|
|||||||
@ -82,7 +82,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
if let arguments = subject.arguments {
|
if let arguments = subject.arguments {
|
||||||
let peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId]
|
var peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId]
|
||||||
|
if let fromPeerId = arguments.fromPeerId {
|
||||||
|
peerIds.append(fromPeerId)
|
||||||
|
}
|
||||||
self.disposable = (context.engine.data.get(
|
self.disposable = (context.engine.data.get(
|
||||||
EngineDataMap(
|
EngineDataMap(
|
||||||
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
|
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
|
||||||
@ -204,10 +207,14 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
descriptionText = "You converted this gift to \(convertStars) Stars. [More About Stars >]()"
|
descriptionText = "You converted this gift to \(convertStars) Stars. [More About Stars >]()"
|
||||||
}
|
}
|
||||||
} else if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] {
|
} else if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] {
|
||||||
|
if case .message = component.subject {
|
||||||
descriptionText = "\(peer.compactDisplayTitle) can keep this gift in their Profile or convert it to \(convertStars) Stars. [More About Stars >]()"
|
descriptionText = "\(peer.compactDisplayTitle) can keep this gift in their Profile or convert it to \(convertStars) Stars. [More About Stars >]()"
|
||||||
} else {
|
} else {
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
descriptionText = ""
|
||||||
|
}
|
||||||
if let spaceRegex {
|
if let spaceRegex {
|
||||||
let nsRange = NSRange(descriptionText.startIndex..., in: descriptionText)
|
let nsRange = NSRange(descriptionText.startIndex..., in: descriptionText)
|
||||||
let matches = spaceRegex.matches(in: descriptionText, options: [], range: nsRange)
|
let matches = spaceRegex.matches(in: descriptionText, options: [], range: nsRange)
|
||||||
@ -273,10 +280,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
let tableLinkColor = theme.list.itemAccentColor
|
let tableLinkColor = theme.list.itemAccentColor
|
||||||
var tableItems: [TableComponent.Item] = []
|
var tableItems: [TableComponent.Item] = []
|
||||||
|
|
||||||
if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] {
|
if let peerId = component.subject.arguments?.fromPeerId, let peer = state.peerMap[peerId] {
|
||||||
tableItems.append(.init(
|
tableItems.append(.init(
|
||||||
id: "to",
|
id: "from",
|
||||||
title: incoming ? strings.Stars_Transaction_From : strings.Stars_Transaction_To,
|
title: strings.Stars_Transaction_From,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
@ -287,24 +294,24 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
action: {
|
action: {
|
||||||
if "".isEmpty {
|
// if "".isEmpty {
|
||||||
component.openPeer(peer)
|
// component.openPeer(peer)
|
||||||
Queue.mainQueue().after(1.0, {
|
// Queue.mainQueue().after(1.0, {
|
||||||
component.cancel(false)
|
// component.cancel(false)
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
if let controller = controller() as? GiftViewScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController {
|
if let controller = controller() as? GiftViewScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController {
|
||||||
chatController.playShakeAnimation()
|
chatController.playShakeAnimation()
|
||||||
}
|
}
|
||||||
component.cancel(true)
|
component.cancel(true)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
tableItems.append(.init(
|
tableItems.append(.init(
|
||||||
id: "from",
|
id: "from_anon",
|
||||||
title: strings.Stars_Transaction_From,
|
title: strings.Stars_Transaction_From,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
PeerCellComponent(
|
PeerCellComponent(
|
||||||
@ -430,6 +437,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: descriptionOrigin + description.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: descriptionOrigin + description.size.height / 2.0))
|
||||||
)
|
)
|
||||||
originY += description.size.height + 10.0
|
originY += description.size.height + 10.0
|
||||||
|
} else {
|
||||||
|
originY += 11.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let amountSpacing: CGFloat = 1.0
|
let amountSpacing: CGFloat = 1.0
|
||||||
@ -439,7 +448,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
var amountOrigin = originY
|
var amountOrigin = originY
|
||||||
if "".isEmpty {
|
if "".isEmpty {
|
||||||
amountOrigin -= descriptionSize.height + 10.0
|
amountOrigin -= descriptionSize.height + 10.0
|
||||||
|
if descriptionSize.height > 0 {
|
||||||
originY += amount.size.height + 26.0
|
originY += amount.size.height + 26.0
|
||||||
|
} else {
|
||||||
|
originY += amount.size.height + 2.0
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
originY += amount.size.height + 20.0
|
originY += amount.size.height + 20.0
|
||||||
}
|
}
|
||||||
@ -696,14 +709,14 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
case message(EngineMessage)
|
case message(EngineMessage)
|
||||||
case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift)
|
case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift)
|
||||||
|
|
||||||
var arguments: (peerId: EnginePeer.Id, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, convertStars: Int64, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool)? {
|
var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, convertStars: Int64, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool)? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .message(message):
|
case let .message(message):
|
||||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted) = action.action {
|
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted) = action.action {
|
||||||
return (message.id.peerId, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, convertStars, text, entities, nameHidden, savedToProfile, converted)
|
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, convertStars, text, entities, nameHidden, savedToProfile, converted)
|
||||||
}
|
}
|
||||||
case let .profileGift(peerId, gift):
|
case let .profileGift(peerId, gift):
|
||||||
return (peerId, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false)
|
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -792,9 +805,25 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .sticker(context: context, file: arguments.gift.file, loop: false, title: added ? "Gift Saved to Profile" : "Gift Removed from Profile", text: added ? "The gift is now displayed in [your profile]()." : "The gift is no longer displayed in [your profile]().", undoText: nil, customAction: nil),
|
content: .sticker(context: context, file: arguments.gift.file, loop: false, title: added ? "Gift Saved to Profile" : "Gift Removed from Profile", text: added ? "The gift is now displayed in [your profile]()." : "The gift is no longer displayed in [your profile]().", undoText: nil, customAction: nil),
|
||||||
elevatedLayout: lastController is ChatController,
|
elevatedLayout: lastController is ChatController,
|
||||||
action: { action in
|
action: { [weak navigationController] action in
|
||||||
if case .info = action {
|
if case .info = action, let navigationController {
|
||||||
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak navigationController] peer in
|
||||||
|
guard let peer, let navigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let controller = context.sharedContext.makePeerInfoController(
|
||||||
|
context: context,
|
||||||
|
updatedPresentationData: nil,
|
||||||
|
peer: peer._asPeer(),
|
||||||
|
mode: .myProfileGifts,
|
||||||
|
avatarInitiallyExpanded: false,
|
||||||
|
fromChat: false,
|
||||||
|
requestsContext: nil
|
||||||
|
) {
|
||||||
|
navigationController.pushViewController(controller, animated: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -825,6 +854,10 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
self?.dismissAnimated()
|
self?.dismissAnimated()
|
||||||
|
|
||||||
if let navigationController {
|
if let navigationController {
|
||||||
|
if let starsContext = context.starsContext {
|
||||||
|
navigationController.pushViewController(context.sharedContext.makeStarsTransactionsScreen(context: context, starsContext: starsContext), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
Queue.mainQueue().after(0.5) {
|
Queue.mainQueue().after(0.5) {
|
||||||
if let lastController = navigationController.viewControllers.last as? ViewController {
|
if let lastController = navigationController.viewControllers.last as? ViewController {
|
||||||
let resultController = UndoOverlayController(
|
let resultController = UndoOverlayController(
|
||||||
|
|||||||
@ -10,12 +10,15 @@ swift_library(
|
|||||||
"-warnings-as-errors",
|
"-warnings-as-errors",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/Display",
|
"//submodules/Display",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/TelegramPresentationData",
|
"//submodules/TelegramPresentationData",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
|
|||||||
@ -2,10 +2,13 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import ListSectionComponent
|
import ListSectionComponent
|
||||||
import TextFieldComponent
|
import TextFieldComponent
|
||||||
|
import LottieComponent
|
||||||
|
import PlainButtonComponent
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
|
||||||
public final class ListMultilineTextFieldItemComponent: Component {
|
public final class ListMultilineTextFieldItemComponent: Component {
|
||||||
@ -14,6 +17,11 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
public fileprivate(set) var text: NSAttributedString = NSAttributedString()
|
public fileprivate(set) var text: NSAttributedString = NSAttributedString()
|
||||||
public fileprivate(set) var isEditing: Bool = false
|
public fileprivate(set) var isEditing: Bool = false
|
||||||
|
|
||||||
|
public var hasTrackingView = false
|
||||||
|
|
||||||
|
public var currentEmojiSuggestion: TextFieldComponent.EmojiSuggestion?
|
||||||
|
public var dismissedEmojiSuggestionPosition: TextFieldComponent.EmojiSuggestion.Position?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,6 +38,11 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum InputMode {
|
||||||
|
case keyboard
|
||||||
|
case emoji
|
||||||
|
}
|
||||||
|
|
||||||
public enum EmptyLineHandling {
|
public enum EmptyLineHandling {
|
||||||
case allowed
|
case allowed
|
||||||
case oneConsecutive
|
case oneConsecutive
|
||||||
@ -49,10 +62,13 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
public let characterLimit: Int?
|
public let characterLimit: Int?
|
||||||
public let displayCharacterLimit: Bool
|
public let displayCharacterLimit: Bool
|
||||||
public let emptyLineHandling: EmptyLineHandling
|
public let emptyLineHandling: EmptyLineHandling
|
||||||
|
public let formatMenuAvailability: TextFieldComponent.FormatMenuAvailability
|
||||||
public let updated: ((String) -> Void)?
|
public let updated: ((String) -> Void)?
|
||||||
public let returnKeyAction: (() -> Void)?
|
public let returnKeyAction: (() -> Void)?
|
||||||
public let backspaceKeyAction: (() -> Void)?
|
public let backspaceKeyAction: (() -> Void)?
|
||||||
public let textUpdateTransition: ComponentTransition
|
public let textUpdateTransition: ComponentTransition
|
||||||
|
public let inputMode: InputMode?
|
||||||
|
public let toggleInputMode: (() -> Void)?
|
||||||
public let tag: AnyObject?
|
public let tag: AnyObject?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -69,10 +85,13 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
characterLimit: Int? = nil,
|
characterLimit: Int? = nil,
|
||||||
displayCharacterLimit: Bool = false,
|
displayCharacterLimit: Bool = false,
|
||||||
emptyLineHandling: EmptyLineHandling = .allowed,
|
emptyLineHandling: EmptyLineHandling = .allowed,
|
||||||
|
formatMenuAvailability: TextFieldComponent.FormatMenuAvailability = .none,
|
||||||
updated: ((String) -> Void)? = nil,
|
updated: ((String) -> Void)? = nil,
|
||||||
returnKeyAction: (() -> Void)? = nil,
|
returnKeyAction: (() -> Void)? = nil,
|
||||||
backspaceKeyAction: (() -> Void)? = nil,
|
backspaceKeyAction: (() -> Void)? = nil,
|
||||||
textUpdateTransition: ComponentTransition = .immediate,
|
textUpdateTransition: ComponentTransition = .immediate,
|
||||||
|
inputMode: InputMode? = nil,
|
||||||
|
toggleInputMode: (() -> Void)? = nil,
|
||||||
tag: AnyObject? = nil
|
tag: AnyObject? = nil
|
||||||
) {
|
) {
|
||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
@ -88,10 +107,13 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
self.characterLimit = characterLimit
|
self.characterLimit = characterLimit
|
||||||
self.displayCharacterLimit = displayCharacterLimit
|
self.displayCharacterLimit = displayCharacterLimit
|
||||||
self.emptyLineHandling = emptyLineHandling
|
self.emptyLineHandling = emptyLineHandling
|
||||||
|
self.formatMenuAvailability = formatMenuAvailability
|
||||||
self.updated = updated
|
self.updated = updated
|
||||||
self.returnKeyAction = returnKeyAction
|
self.returnKeyAction = returnKeyAction
|
||||||
self.backspaceKeyAction = backspaceKeyAction
|
self.backspaceKeyAction = backspaceKeyAction
|
||||||
self.textUpdateTransition = textUpdateTransition
|
self.textUpdateTransition = textUpdateTransition
|
||||||
|
self.inputMode = inputMode
|
||||||
|
self.toggleInputMode = toggleInputMode
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,9 +157,15 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (lhs.updated == nil) != (rhs.updated == nil) {
|
if (lhs.updated == nil) != (rhs.updated == nil) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.inputMode != rhs.inputMode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +173,8 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
private let textField = ComponentView<Empty>()
|
private let textField = ComponentView<Empty>()
|
||||||
private let textFieldExternalState = TextFieldComponent.ExternalState()
|
private let textFieldExternalState = TextFieldComponent.ExternalState()
|
||||||
|
|
||||||
|
private var modeSelector: ComponentView<Empty>?
|
||||||
|
|
||||||
private let placeholder = ComponentView<Empty>()
|
private let placeholder = ComponentView<Empty>()
|
||||||
private var customPlaceholder: ComponentView<Empty>?
|
private var customPlaceholder: ComponentView<Empty>?
|
||||||
|
|
||||||
@ -203,17 +233,40 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func insertText(text: NSAttributedString) {
|
||||||
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
|
textFieldView.insertText(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func backwardsDeleteText() {
|
||||||
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
|
textFieldView.deleteBackward()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var textFieldView: TextFieldComponent.View? {
|
||||||
|
return self.textField.view as? TextFieldComponent.View
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: ListMultilineTextFieldItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: ListMultilineTextFieldItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
self.isUpdating = false
|
self.isUpdating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousComponent = self.component
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
let verticalInset: CGFloat = 12.0
|
let verticalInset: CGFloat = 12.0
|
||||||
let sideInset: CGFloat = 16.0
|
let leftInset: CGFloat = 16.0
|
||||||
|
var rightInset: CGFloat = 16.0
|
||||||
|
let modeSelectorSize = CGSize(width: 32.0, height: 32.0)
|
||||||
|
|
||||||
|
if component.inputMode != nil {
|
||||||
|
rightInset += 34.0
|
||||||
|
}
|
||||||
|
|
||||||
let textLimitFont = Font.regular(15.0)
|
let textLimitFont = Font.regular(15.0)
|
||||||
var measureTextLimitInset: CGFloat = 0.0
|
var measureTextLimitInset: CGFloat = 0.0
|
||||||
@ -258,8 +311,8 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
fontSize: 17.0,
|
fontSize: 17.0,
|
||||||
textColor: component.theme.list.itemPrimaryTextColor,
|
textColor: component.theme.list.itemPrimaryTextColor,
|
||||||
accentColor: component.theme.list.itemPrimaryTextColor,
|
accentColor: component.theme.list.itemPrimaryTextColor,
|
||||||
insets: UIEdgeInsets(top: verticalInset, left: sideInset - 8.0, bottom: verticalInset, right: sideInset - 8.0 + measureTextLimitInset),
|
insets: UIEdgeInsets(top: verticalInset, left: leftInset - 8.0, bottom: verticalInset, right: rightInset - 8.0 + measureTextLimitInset),
|
||||||
hideKeyboard: false,
|
hideKeyboard: component.inputMode == .emoji,
|
||||||
customInputView: nil,
|
customInputView: nil,
|
||||||
resetText: component.resetText.flatMap { resetText in
|
resetText: component.resetText.flatMap { resetText in
|
||||||
return NSAttributedString(string: resetText.value, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)
|
return NSAttributedString(string: resetText.value, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)
|
||||||
@ -267,7 +320,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
isOneLineWhenUnfocused: false,
|
isOneLineWhenUnfocused: false,
|
||||||
characterLimit: component.characterLimit,
|
characterLimit: component.characterLimit,
|
||||||
emptyLineHandling: mappedEmptyLineHandling,
|
emptyLineHandling: mappedEmptyLineHandling,
|
||||||
formatMenuAvailability: .none,
|
formatMenuAvailability: component.formatMenuAvailability,
|
||||||
returnKeyType: component.returnKeyType,
|
returnKeyType: component.returnKeyType,
|
||||||
lockedFormatAction: {
|
lockedFormatAction: {
|
||||||
},
|
},
|
||||||
@ -309,9 +362,9 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
text: .plain(NSAttributedString(string: component.placeholder.isEmpty ? " " : component.placeholder, font: Font.regular(17.0), textColor: component.theme.list.itemPlaceholderTextColor))
|
text: .plain(NSAttributedString(string: component.placeholder.isEmpty ? " " : component.placeholder, font: Font.regular(17.0), textColor: component.theme.list.itemPlaceholderTextColor))
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
|
||||||
)
|
)
|
||||||
let placeholderFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: placeholderSize)
|
let placeholderFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: placeholderSize)
|
||||||
if let placeholderView = self.placeholder.view {
|
if let placeholderView = self.placeholder.view {
|
||||||
if placeholderView.superview == nil {
|
if placeholderView.superview == nil {
|
||||||
placeholderView.layer.anchorPoint = CGPoint()
|
placeholderView.layer.anchorPoint = CGPoint()
|
||||||
@ -329,6 +382,9 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
component.externalState?.hasText = self.textFieldExternalState.hasText
|
component.externalState?.hasText = self.textFieldExternalState.hasText
|
||||||
component.externalState?.text = self.textFieldExternalState.text
|
component.externalState?.text = self.textFieldExternalState.text
|
||||||
component.externalState?.isEditing = self.textFieldExternalState.isEditing
|
component.externalState?.isEditing = self.textFieldExternalState.isEditing
|
||||||
|
component.externalState?.currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion
|
||||||
|
component.externalState?.dismissedEmojiSuggestionPosition = self.textFieldExternalState.dismissedEmojiSuggestionPosition
|
||||||
|
component.externalState?.hasTrackingView = self.textFieldExternalState.hasTrackingView
|
||||||
|
|
||||||
var displayRemainingLimit: Int?
|
var displayRemainingLimit: Int?
|
||||||
if let characterLimit = component.characterLimit, component.displayCharacterLimit {
|
if let characterLimit = component.characterLimit, component.displayCharacterLimit {
|
||||||
@ -357,7 +413,7 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
)
|
)
|
||||||
let textLimitLabelFrame = CGRect(origin: CGPoint(x: availableSize.width - textLimitLabelSize.width - sideInset, y: verticalInset + 2.0), size: textLimitLabelSize)
|
let textLimitLabelFrame = CGRect(origin: CGPoint(x: availableSize.width - textLimitLabelSize.width - rightInset, y: verticalInset + 2.0), size: textLimitLabelSize)
|
||||||
if let textLimitLabelView = textLimitLabel.view {
|
if let textLimitLabelView = textLimitLabel.view {
|
||||||
if textLimitLabelView.superview == nil {
|
if textLimitLabelView.superview == nil {
|
||||||
textLimitLabelView.isUserInteractionEnabled = false
|
textLimitLabelView.isUserInteractionEnabled = false
|
||||||
@ -374,6 +430,91 @@ public final class ListMultilineTextFieldItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let inputMode = component.inputMode {
|
||||||
|
var modeSelectorTransition = transition
|
||||||
|
let modeSelector: ComponentView<Empty>
|
||||||
|
if let current = self.modeSelector {
|
||||||
|
modeSelector = current
|
||||||
|
} else {
|
||||||
|
modeSelectorTransition = modeSelectorTransition.withAnimation(.none)
|
||||||
|
modeSelector = ComponentView()
|
||||||
|
self.modeSelector = modeSelector
|
||||||
|
}
|
||||||
|
let animationName: String
|
||||||
|
var playAnimation = false
|
||||||
|
if let previousComponent, let previousInputMode = previousComponent.inputMode {
|
||||||
|
if previousInputMode != inputMode {
|
||||||
|
playAnimation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch inputMode {
|
||||||
|
case .keyboard:
|
||||||
|
animationName = "input_anim_keyToSmile"
|
||||||
|
case .emoji:
|
||||||
|
animationName = "input_anim_smileToKey"
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = modeSelector.update(
|
||||||
|
transition: modeSelectorTransition,
|
||||||
|
component: AnyComponent(PlainButtonComponent(
|
||||||
|
content: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(
|
||||||
|
name: animationName
|
||||||
|
),
|
||||||
|
color: component.theme.chat.inputPanel.inputControlColor.blitOver(component.theme.list.itemBlocksBackgroundColor, alpha: 1.0),
|
||||||
|
size: modeSelectorSize
|
||||||
|
)),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.toggleInputMode?()
|
||||||
|
},
|
||||||
|
animateScale: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: modeSelectorSize
|
||||||
|
)
|
||||||
|
let modeSelectorFrame = CGRect(origin: CGPoint(x: size.width - 4.0 - modeSelectorSize.width, y: floor((size.height - modeSelectorSize.height) * 0.5)), size: modeSelectorSize)
|
||||||
|
if let modeSelectorView = modeSelector.view as? PlainButtonComponent.View {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
|
||||||
|
if modeSelectorView.superview == nil {
|
||||||
|
self.addSubview(modeSelectorView)
|
||||||
|
ComponentTransition.immediate.setAlpha(view: modeSelectorView, alpha: 0.0)
|
||||||
|
ComponentTransition.immediate.setScale(view: modeSelectorView, scale: 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
if playAnimation, let animationView = modeSelectorView.contentView as? LottieComponent.View {
|
||||||
|
animationView.playOnce()
|
||||||
|
}
|
||||||
|
|
||||||
|
modeSelectorTransition.setPosition(view: modeSelectorView, position: modeSelectorFrame.center)
|
||||||
|
modeSelectorTransition.setBounds(view: modeSelectorView, bounds: CGRect(origin: CGPoint(), size: modeSelectorFrame.size))
|
||||||
|
|
||||||
|
if let externalState = component.externalState {
|
||||||
|
let displaySelector = externalState.isEditing
|
||||||
|
|
||||||
|
alphaTransition.setAlpha(view: modeSelectorView, alpha: displaySelector ? 1.0 : 0.0)
|
||||||
|
alphaTransition.setScale(view: modeSelectorView, scale: displaySelector ? 1.0 : 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let modeSelector = self.modeSelector {
|
||||||
|
self.modeSelector = nil
|
||||||
|
if let modeSelectorView = modeSelector.view {
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||||
|
alphaTransition.setAlpha(view: modeSelectorView, alpha: 0.0, completion: { [weak modeSelectorView] _ in
|
||||||
|
modeSelectorView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
alphaTransition.setScale(view: modeSelectorView, scale: 0.001)
|
||||||
|
} else {
|
||||||
|
modeSelectorView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,17 +41,20 @@ public final class ListSectionContentView: UIView {
|
|||||||
|
|
||||||
public final class Configuration {
|
public final class Configuration {
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
|
public let isModal: Bool
|
||||||
public let displaySeparators: Bool
|
public let displaySeparators: Bool
|
||||||
public let extendsItemHighlightToSection: Bool
|
public let extendsItemHighlightToSection: Bool
|
||||||
public let background: ListSectionComponent.Background
|
public let background: ListSectionComponent.Background
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
|
isModal: Bool = false,
|
||||||
displaySeparators: Bool,
|
displaySeparators: Bool,
|
||||||
extendsItemHighlightToSection: Bool,
|
extendsItemHighlightToSection: Bool,
|
||||||
background: ListSectionComponent.Background
|
background: ListSectionComponent.Background
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
self.isModal = isModal
|
||||||
self.displaySeparators = displaySeparators
|
self.displaySeparators = displaySeparators
|
||||||
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
||||||
self.background = background
|
self.background = background
|
||||||
@ -116,7 +119,7 @@ public final class ListSectionContentView: UIView {
|
|||||||
backgroundColor = configuration.theme.list.itemHighlightedBackgroundColor
|
backgroundColor = configuration.theme.list.itemHighlightedBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
transition = .easeInOut(duration: 0.2)
|
transition = .easeInOut(duration: 0.2)
|
||||||
backgroundColor = configuration.theme.list.itemBlocksBackgroundColor
|
backgroundColor = configuration.isModal ? configuration.theme.list.itemModalBlocksBackgroundColor : configuration.theme.list.itemBlocksBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
self.externalContentBackgroundView.updateColor(color: backgroundColor, transition: transition)
|
self.externalContentBackgroundView.updateColor(color: backgroundColor, transition: transition)
|
||||||
@ -144,7 +147,7 @@ public final class ListSectionContentView: UIView {
|
|||||||
if self.highlightedItemId != nil && configuration.extendsItemHighlightToSection {
|
if self.highlightedItemId != nil && configuration.extendsItemHighlightToSection {
|
||||||
backgroundColor = configuration.theme.list.itemHighlightedBackgroundColor
|
backgroundColor = configuration.theme.list.itemHighlightedBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
backgroundColor = configuration.theme.list.itemBlocksBackgroundColor
|
backgroundColor = configuration.isModal ? configuration.theme.list.itemModalBlocksBackgroundColor : configuration.theme.list.itemBlocksBackgroundColor
|
||||||
}
|
}
|
||||||
self.externalContentBackgroundView.updateColor(color: backgroundColor, transition: transition)
|
self.externalContentBackgroundView.updateColor(color: backgroundColor, transition: transition)
|
||||||
|
|
||||||
@ -305,6 +308,7 @@ public final class ListSectionComponent: Component {
|
|||||||
public let header: AnyComponent<Empty>?
|
public let header: AnyComponent<Empty>?
|
||||||
public let footer: AnyComponent<Empty>?
|
public let footer: AnyComponent<Empty>?
|
||||||
public let items: [AnyComponentWithIdentity<Empty>]
|
public let items: [AnyComponentWithIdentity<Empty>]
|
||||||
|
public let isModal: Bool
|
||||||
public let displaySeparators: Bool
|
public let displaySeparators: Bool
|
||||||
public let extendsItemHighlightToSection: Bool
|
public let extendsItemHighlightToSection: Bool
|
||||||
|
|
||||||
@ -314,6 +318,7 @@ public final class ListSectionComponent: Component {
|
|||||||
header: AnyComponent<Empty>?,
|
header: AnyComponent<Empty>?,
|
||||||
footer: AnyComponent<Empty>?,
|
footer: AnyComponent<Empty>?,
|
||||||
items: [AnyComponentWithIdentity<Empty>],
|
items: [AnyComponentWithIdentity<Empty>],
|
||||||
|
isModal: Bool = false,
|
||||||
displaySeparators: Bool = true,
|
displaySeparators: Bool = true,
|
||||||
extendsItemHighlightToSection: Bool = false
|
extendsItemHighlightToSection: Bool = false
|
||||||
) {
|
) {
|
||||||
@ -322,6 +327,7 @@ public final class ListSectionComponent: Component {
|
|||||||
self.header = header
|
self.header = header
|
||||||
self.footer = footer
|
self.footer = footer
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.isModal = isModal
|
||||||
self.displaySeparators = displaySeparators
|
self.displaySeparators = displaySeparators
|
||||||
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
self.extendsItemHighlightToSection = extendsItemHighlightToSection
|
||||||
}
|
}
|
||||||
@ -342,6 +348,9 @@ public final class ListSectionComponent: Component {
|
|||||||
if lhs.items != rhs.items {
|
if lhs.items != rhs.items {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isModal != rhs.isModal {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.displaySeparators != rhs.displaySeparators {
|
if lhs.displaySeparators != rhs.displaySeparators {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -448,6 +457,7 @@ public final class ListSectionComponent: Component {
|
|||||||
let contentResult = self.contentView.update(
|
let contentResult = self.contentView.update(
|
||||||
configuration: ListSectionContentView.Configuration(
|
configuration: ListSectionContentView.Configuration(
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
|
isModal: component.isModal,
|
||||||
displaySeparators: component.displaySeparators,
|
displaySeparators: component.displaySeparators,
|
||||||
extendsItemHighlightToSection: component.extendsItemHighlightToSection,
|
extendsItemHighlightToSection: component.extendsItemHighlightToSection,
|
||||||
background: component.background
|
background: component.background
|
||||||
@ -522,17 +532,20 @@ public final class ListSubSectionComponent: Component {
|
|||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let leftInset: CGFloat
|
public let leftInset: CGFloat
|
||||||
public let items: [AnyComponentWithIdentity<Empty>]
|
public let items: [AnyComponentWithIdentity<Empty>]
|
||||||
|
public let isModal: Bool
|
||||||
public let displaySeparators: Bool
|
public let displaySeparators: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
leftInset: CGFloat,
|
leftInset: CGFloat,
|
||||||
items: [AnyComponentWithIdentity<Empty>],
|
items: [AnyComponentWithIdentity<Empty>],
|
||||||
|
isModal: Bool = false,
|
||||||
displaySeparators: Bool = true
|
displaySeparators: Bool = true
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.leftInset = leftInset
|
self.leftInset = leftInset
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.isModal = isModal
|
||||||
self.displaySeparators = displaySeparators
|
self.displaySeparators = displaySeparators
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,6 +559,9 @@ public final class ListSubSectionComponent: Component {
|
|||||||
if lhs.items != rhs.items {
|
if lhs.items != rhs.items {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isModal != rhs.isModal {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.displaySeparators != rhs.displaySeparators {
|
if lhs.displaySeparators != rhs.displaySeparators {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -615,6 +631,7 @@ public final class ListSubSectionComponent: Component {
|
|||||||
let contentResult = self.contentView.update(
|
let contentResult = self.contentView.update(
|
||||||
configuration: ListSectionContentView.Configuration(
|
configuration: ListSectionContentView.Configuration(
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
|
isModal: component.isModal,
|
||||||
displaySeparators: component.displaySeparators,
|
displaySeparators: component.displaySeparators,
|
||||||
extendsItemHighlightToSection: false,
|
extendsItemHighlightToSection: false,
|
||||||
background: .none(clipped: false)
|
background: .none(clipped: false)
|
||||||
|
|||||||
@ -8250,9 +8250,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openReport(type: PeerInfoReportType, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)?) {
|
private func openReport(type: PeerInfoReportType, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)?) {
|
||||||
guard let controller = self.controller else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
@ -8291,20 +8288,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
])
|
])
|
||||||
self.controller?.present(actionSheet, in: .window(.root))
|
self.controller?.present(actionSheet, in: .window(.root))
|
||||||
default:
|
default:
|
||||||
let options: [PeerReportOption]
|
contextController?.dismiss()
|
||||||
if case .user = type {
|
|
||||||
options = [.spam, .fake, .violence, .pornography, .childAbuse]
|
self.context.sharedContext.makeContentReportScreen(context: self.context, subject: .peer(self.peerId), forceDark: false, present: { [weak self] controller in
|
||||||
} else {
|
self?.controller?.push(controller)
|
||||||
options = [.spam, .fake, .violence, .pornography, .childAbuse, .copyright, .other]
|
}, completion: {
|
||||||
}
|
|
||||||
|
|
||||||
presentPeerReportOptions(context: self.context, parent: controller, contextController: contextController, backAction: backAction, subject: .peer(self.peerId), options: options, passthrough: true, completion: { [weak self] reason, _ in
|
|
||||||
if let reason = reason {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self?.openChatForReporting(reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// let options: [PeerReportOption]
|
||||||
|
// if case .user = type {
|
||||||
|
// options = [.spam, .fake, .violence, .pornography, .childAbuse]
|
||||||
|
// } else {
|
||||||
|
// options = [.spam, .fake, .violence, .pornography, .childAbuse, .copyright, .other]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// presentPeerReportOptions(context: self.context, parent: controller, contextController: contextController, backAction: backAction, subject: .peer(self.peerId), options: options, passthrough: true, completion: { [weak self] reason, _ in
|
||||||
|
// if let reason = reason {
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// self?.openChatForReporting(reason)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11645,13 +11650,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
strongSelf.view.endEditing(true)
|
strongSelf.view.endEditing(true)
|
||||||
|
|
||||||
strongSelf.controller?.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), passthrough: false, present: { c, a in
|
strongSelf.context.sharedContext.makeContentReportScreen(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), forceDark: false, present: { [weak self] controller in
|
||||||
self?.controller?.present(c, in: .window(.root), with: a)
|
self?.controller?.push(controller)
|
||||||
}, push: { c in
|
}, completion: {})
|
||||||
self?.controller?.push(c)
|
|
||||||
}, completion: { _, _ in }), in: .window(.root))
|
|
||||||
|
|
||||||
|
|
||||||
}, displayCopyProtectionTip: { [weak self] node, save in
|
}, displayCopyProtectionTip: { [weak self] node, save in
|
||||||
if let strongSelf = self, let peer = strongSelf.data?.peer, let messageIds = strongSelf.state.selectedMessageIds, !messageIds.isEmpty {
|
if let strongSelf = self, let peer = strongSelf.data?.peer, let messageIds = strongSelf.state.selectedMessageIds, !messageIds.isEmpty {
|
||||||
let _ = (strongSelf.context.engine.data.get(EngineDataMap(
|
let _ = (strongSelf.context.engine.data.get(EngineDataMap(
|
||||||
@ -12284,6 +12285,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||||||
private weak var requestsContext: PeerInvitationImportersContext?
|
private weak var requestsContext: PeerInvitationImportersContext?
|
||||||
fileprivate let starsContext: StarsContext?
|
fileprivate let starsContext: StarsContext?
|
||||||
private let switchToRecommendedChannels: Bool
|
private let switchToRecommendedChannels: Bool
|
||||||
|
private let switchToGifts: Bool
|
||||||
private let chatLocation: ChatLocation
|
private let chatLocation: ChatLocation
|
||||||
private let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
private let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||||
|
|
||||||
@ -12340,7 +12342,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||||||
|
|
||||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false) {
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false, switchToGifts: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
@ -12354,6 +12356,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||||||
self.hintGroupInCommon = hintGroupInCommon
|
self.hintGroupInCommon = hintGroupInCommon
|
||||||
self.requestsContext = requestsContext
|
self.requestsContext = requestsContext
|
||||||
self.switchToRecommendedChannels = switchToRecommendedChannels
|
self.switchToRecommendedChannels = switchToRecommendedChannels
|
||||||
|
self.switchToGifts = switchToGifts
|
||||||
|
|
||||||
if let forumTopicThread = forumTopicThread {
|
if let forumTopicThread = forumTopicThread {
|
||||||
self.chatLocation = .replyThread(message: forumTopicThread)
|
self.chatLocation = .replyThread(message: forumTopicThread)
|
||||||
@ -12694,7 +12697,13 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil)
|
var initialPaneKey: PeerInfoPaneKey?
|
||||||
|
if self.switchToRecommendedChannels {
|
||||||
|
initialPaneKey = .recommended
|
||||||
|
} else if self.switchToGifts {
|
||||||
|
initialPaneKey = .gifts
|
||||||
|
}
|
||||||
|
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: initialPaneKey)
|
||||||
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
|
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
|
||||||
self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
|
self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
|
||||||
self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())
|
self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())
|
||||||
|
|||||||
@ -87,6 +87,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let isFirstTime = starsProducts == nil
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.statusPromise.set(.single(PeerInfoStatusData(text: presentationData.strings.SharedMedia_GiftCount(state.count ?? 0), isActivity: true, key: .gifts)))
|
self.statusPromise.set(.single(PeerInfoStatusData(text: presentationData.strings.SharedMedia_GiftCount(state.count ?? 0), isActivity: true, key: .gifts)))
|
||||||
self.starsProducts = state.gifts
|
self.starsProducts = state.gifts
|
||||||
@ -96,7 +97,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
self.ready.set(.single(true))
|
self.ready.set(.single(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateScrolling()
|
self.updateScrolling(transition: isFirstTime ? .immediate : .easeInOut(duration: 0.25))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,10 +120,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
self.updateScrolling()
|
self.updateScrolling(transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateScrolling() {
|
func updateScrolling(transition: ComponentTransition) {
|
||||||
if let starsProducts = self.starsProducts, let params = self.currentParams {
|
if let starsProducts = self.starsProducts, let params = self.currentParams {
|
||||||
let optionSpacing: CGFloat = 10.0
|
let optionSpacing: CGFloat = 10.0
|
||||||
let sideInset = params.sideInset + 16.0
|
let sideInset = params.sideInset + 16.0
|
||||||
@ -140,13 +141,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
let itemId = AnyHashable(product.date)
|
let itemId = AnyHashable(product.date)
|
||||||
validIds.append(itemId)
|
validIds.append(itemId)
|
||||||
|
|
||||||
let itemTransition = ComponentTransition.immediate
|
var itemTransition = transition
|
||||||
let visibleItem: ComponentView<Empty>
|
let visibleItem: ComponentView<Empty>
|
||||||
if let current = self.starsItems[itemId] {
|
if let current = self.starsItems[itemId] {
|
||||||
visibleItem = current
|
visibleItem = current
|
||||||
} else {
|
} else {
|
||||||
visibleItem = ComponentView()
|
visibleItem = ComponentView()
|
||||||
self.starsItems[itemId] = visibleItem
|
self.starsItems[itemId] = visibleItem
|
||||||
|
itemTransition = .immediate
|
||||||
}
|
}
|
||||||
|
|
||||||
var isVisible = false
|
var isVisible = false
|
||||||
@ -221,6 +223,26 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var removeIds: [AnyHashable] = []
|
||||||
|
for (id, item) in self.starsItems {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
if let itemView = item.view {
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||||
|
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||||
|
itemView.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
itemView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.starsItems.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * starsOptionSize.height + 60.0 + 16.0
|
var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * starsOptionSize.height + 60.0 + 16.0
|
||||||
|
|
||||||
if self.peerId == self.context.account.peerId {
|
if self.peerId == self.context.account.peerId {
|
||||||
@ -354,7 +376,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
self.scrollNode.view.isScrollEnabled = !isScrollingLockedAtTop
|
self.scrollNode.view.isScrollEnabled = !isScrollingLockedAtTop
|
||||||
|
|
||||||
self.updateScrolling()
|
self.updateScrolling(transition: ComponentTransition(transition))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func findLoadedMessage(id: MessageId) -> Message? {
|
public func findLoadedMessage(id: MessageId) -> Message? {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ swift_library(
|
|||||||
"//submodules/SSignalKit/SwiftSignalKit",
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
"//submodules/AttachmentUI",
|
"//submodules/AttachmentUI",
|
||||||
"//submodules/PremiumUI",
|
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -5,15 +5,17 @@ import AsyncDisplayKit
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import PremiumUI
|
|
||||||
import AttachmentUI
|
import AttachmentUI
|
||||||
|
import GiftOptionsScreen
|
||||||
|
|
||||||
public class PremiumGiftAttachmentScreen: PremiumGiftScreen, AttachmentContainable {
|
public class PremiumGiftAttachmentScreen: GiftOptionsScreen, AttachmentContainable {
|
||||||
public var requestAttachmentMenuExpansion: () -> Void = {}
|
public var requestAttachmentMenuExpansion: () -> Void = {}
|
||||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||||
public var parentController: () -> ViewController? = {
|
public var parentController: () -> ViewController? = {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
||||||
|
public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
||||||
public var cancelPanGesture: () -> Void = { }
|
public var cancelPanGesture: () -> Void = { }
|
||||||
public var isContainerPanning: () -> Bool = { return false }
|
public var isContainerPanning: () -> Bool = { return false }
|
||||||
public var isContainerExpanded: () -> Bool = { return false }
|
public var isContainerExpanded: () -> Bool = { return false }
|
||||||
@ -25,17 +27,16 @@ public class PremiumGiftAttachmentScreen: PremiumGiftScreen, AttachmentContainab
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class PremiumGiftContext: AttachmentMediaPickerContext {
|
private final class PremiumGiftContext: AttachmentMediaPickerContext {
|
||||||
private weak var controller: PremiumGiftScreen?
|
private weak var controller: GiftOptionsScreen?
|
||||||
|
|
||||||
public var mainButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
public var mainButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
||||||
return self.controller?.mainButtonStatePromise.get() ?? .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(controller: PremiumGiftScreen) {
|
init(controller: GiftOptionsScreen) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
self.controller?.mainButtonPressed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1037,7 +1037,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .stars(birthdays), completion: { [weak self] peerIds in
|
let controller = self.context.sharedContext.makeStarsGiftController(context: self.context, birthdays: birthdays, completion: { [weak self] peerIds in
|
||||||
guard let self, let peerId = peerIds.first else {
|
guard let self, let peerId = peerIds.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -741,6 +741,9 @@ public final class TextFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.insertText(NSAttributedString(string: insertString))
|
self.insertText(NSAttributedString(string: insertString))
|
||||||
|
} else if (range.length == 0 && text == "\n"), let returnKeyAction = component.returnKeyAction {
|
||||||
|
returnKeyAction()
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -588,19 +588,17 @@ extension ChatControllerImpl {
|
|||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
}
|
}
|
||||||
case .gift:
|
case .gift:
|
||||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, let starsContext = context.starsContext {
|
||||||
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
||||||
if !premiumGiftOptions.isEmpty {
|
if !premiumGiftOptions.isEmpty {
|
||||||
let controller = PremiumGiftAttachmentScreen(context: context, peerIds: [peer.id], options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in
|
let controller = PremiumGiftAttachmentScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumGiftOptions, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
guard let self else {
|
||||||
strongSelf.push(c)
|
return
|
||||||
}
|
|
||||||
}, completion: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.hintPlayNextOutgoingGift()
|
|
||||||
strongSelf.attachmentController?.dismiss(animated: true)
|
|
||||||
}
|
}
|
||||||
|
self.hintPlayNextOutgoingGift()
|
||||||
|
self.attachmentController?.dismiss(animated: true)
|
||||||
})
|
})
|
||||||
|
|
||||||
completion(controller, controller.mediaPickerContext)
|
completion(controller, controller.mediaPickerContext)
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import AttachmentUI
|
|||||||
import SearchBarNode
|
import SearchBarNode
|
||||||
import ChatSendAudioMessageContextPreview
|
import ChatSendAudioMessageContextPreview
|
||||||
import ChatSendMessageActionUI
|
import ChatSendMessageActionUI
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable {
|
class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -42,6 +43,9 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
|||||||
private let multipleSelection: Bool
|
private let multipleSelection: Bool
|
||||||
private let requirePhoneNumbers: Bool
|
private let requirePhoneNumbers: Bool
|
||||||
|
|
||||||
|
private let openProfile: ((EnginePeer) -> Void)?
|
||||||
|
private let sendMessage: ((EnginePeer) -> Void)?
|
||||||
|
|
||||||
private var _ready = Promise<Bool>()
|
private var _ready = Promise<Bool>()
|
||||||
override var ready: Promise<Bool> {
|
override var ready: Promise<Bool> {
|
||||||
return self._ready
|
return self._ready
|
||||||
@ -105,6 +109,9 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
|||||||
self.multipleSelection = params.multipleSelection
|
self.multipleSelection = params.multipleSelection
|
||||||
self.requirePhoneNumbers = params.requirePhoneNumbers
|
self.requirePhoneNumbers = params.requirePhoneNumbers
|
||||||
|
|
||||||
|
self.openProfile = params.openProfile
|
||||||
|
self.sendMessage = params.sendMessage
|
||||||
|
|
||||||
self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
@ -219,15 +226,15 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.contactsNode.requestOpenPeerFromSearch = { [weak self] peer in
|
self.contactsNode.requestOpenPeerFromSearch = { [weak self] peer in
|
||||||
self?.openPeer(peer: peer, action: .generic)
|
self?.openPeer(peer: peer, action: .generic, node: nil, gesture: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.contactsNode.contactListNode.activateSearch = { [weak self] in
|
self.contactsNode.contactListNode.activateSearch = { [weak self] in
|
||||||
self?.activateSearch()
|
self?.activateSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer, action, _, _ in
|
self.contactsNode.contactListNode.openPeer = { [weak self] peer, action, node, gesture in
|
||||||
self?.openPeer(peer: peer, action: action)
|
self?.openPeer(peer: peer, action: action, node: node, gesture: gesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.contactsNode.contactListNode.suppressPermissionWarning = { [weak self] in
|
self.contactsNode.contactListNode.suppressPermissionWarning = { [weak self] in
|
||||||
@ -357,7 +364,40 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openPeer(peer: ContactListPeer, action: ContactListAction) {
|
private func openPeer(peer: ContactListPeer, action: ContactListAction, node: ASDisplayNode?, gesture: ContextGesture?) {
|
||||||
|
if case .more = action {
|
||||||
|
guard case let .peer(peer, _, _) = peer, let node = node as? ContextReferenceContentNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = self.presentationData
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Premium_Gift_ContactSelection_SendMessage, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, iconPosition: .left, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
self.sendMessage?(EnginePeer(peer))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Premium_Gift_ContactSelection_OpenProfile, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, iconPosition: .left, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
self.openProfile?(EnginePeer(peer))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
let contextController = ContextController(presentationData: presentationData, source: .reference(ContactContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
|
self.present(contextController, in: .window(.root))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
self.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
self.confirmationDisposable.set((self.confirmation(peer) |> deliverOnMainQueue).startStrict(next: { [weak self] value in
|
self.confirmationDisposable.set((self.confirmation(peer) |> deliverOnMainQueue).startStrict(next: { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -477,3 +517,17 @@ final class ContactsPickerContext: AttachmentMediaPickerContext {
|
|||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ContactContextReferenceContentSource: ContextReferenceContentSource {
|
||||||
|
private let controller: ViewController
|
||||||
|
private let sourceNode: ContextReferenceContentNode
|
||||||
|
|
||||||
|
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||||
|
self.controller = controller
|
||||||
|
self.sourceNode = sourceNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||||
|
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
|||||||
var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?, _ parameters: ChatSendMessageActionSheetController.SendParameters?) -> Void)?
|
var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?, _ parameters: ChatSendMessageActionSheetController.SendParameters?) -> Void)?
|
||||||
var dismiss: (() -> Void)?
|
var dismiss: (() -> Void)?
|
||||||
var cancelSearch: (() -> Void)?
|
var cancelSearch: (() -> Void)?
|
||||||
|
var openPeerMore: ((ContactListPeer, ASDisplayNode?, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
var presentationData: PresentationData {
|
var presentationData: PresentationData {
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@ -2204,21 +2204,90 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action)
|
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
var presentBirthdayPickerImpl: (() -> Void)?
|
||||||
|
let starsMode: ContactSelectionControllerMode = .starsGifting(birthdays: birthdays, hasActions: false)
|
||||||
|
|
||||||
|
let contactOptions: Signal<[ContactListAdditionalOption], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId))
|
||||||
|
|> map { birthday in
|
||||||
|
if birthday == nil {
|
||||||
|
return [ContactListAdditionalOption(
|
||||||
|
title: presentationData.strings.Premium_Gift_ContactSelection_AddBirthday,
|
||||||
|
icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!),
|
||||||
|
action: {
|
||||||
|
presentBirthdayPickerImpl?()
|
||||||
|
},
|
||||||
|
clearHighlightAutomatically: true
|
||||||
|
)]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|
||||||
|
let options = Promise<[StarsGiftOption]>()
|
||||||
|
options.set(context.engine.payments.starsGiftOptions(peerId: nil))
|
||||||
|
let controller = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(
|
||||||
|
context: context,
|
||||||
|
mode: starsMode,
|
||||||
|
autoDismiss: false,
|
||||||
|
title: { strings in return strings.Stars_Purchase_GiftStars },
|
||||||
|
options: contactOptions
|
||||||
|
))
|
||||||
|
let _ = (controller.result
|
||||||
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
|
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer {
|
||||||
|
completion([peer.id])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
presentBirthdayPickerImpl = { [weak controller] in
|
||||||
|
guard let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .setupBirthday).startStandalone()
|
||||||
|
|
||||||
|
let settingsPromise: Promise<AccountPrivacySettings?>
|
||||||
|
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface, let current = rootController.getPrivacySettings() {
|
||||||
|
settingsPromise = current
|
||||||
|
} else {
|
||||||
|
settingsPromise = Promise()
|
||||||
|
settingsPromise.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init)))
|
||||||
|
}
|
||||||
|
let birthdayController = BirthdayPickerScreen(context: context, settings: settingsPromise.get(), openSettings: {
|
||||||
|
context.sharedContext.makeBirthdayPrivacyController(context: context, settings: settingsPromise, openedFromBirthdayScreen: true, present: { [weak controller] c in
|
||||||
|
controller?.push(c)
|
||||||
|
})
|
||||||
|
}, completion: { [weak controller] value in
|
||||||
|
let _ = context.engine.accountData.updateBirthday(birthday: value).startStandalone()
|
||||||
|
|
||||||
|
controller?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: presentationData.strings.Birthday_Added, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in
|
||||||
|
return true
|
||||||
|
}), in: .current)
|
||||||
|
})
|
||||||
|
controller.push(birthdayController)
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController {
|
public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var presentBirthdayPickerImpl: (() -> Void)?
|
var presentBirthdayPickerImpl: (() -> Void)?
|
||||||
var starsMode: ContactSelectionControllerMode = .generic
|
var mode: ContactSelectionControllerMode = .generic
|
||||||
var currentBirthdays: [EnginePeer.Id: TelegramBirthday]?
|
var currentBirthdays: [EnginePeer.Id: TelegramBirthday]?
|
||||||
|
|
||||||
if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty {
|
if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty {
|
||||||
starsMode = .starsGifting(birthdays: birthdays, hasActions: true)
|
mode = .starsGifting(birthdays: birthdays, hasActions: true)
|
||||||
currentBirthdays = birthdays
|
currentBirthdays = birthdays
|
||||||
} else if case let .settings(birthdays) = source, let birthdays, !birthdays.isEmpty {
|
} else if case let .settings(birthdays) = source, let birthdays, !birthdays.isEmpty {
|
||||||
starsMode = .starsGifting(birthdays: birthdays, hasActions: true)
|
mode = .starsGifting(birthdays: birthdays, hasActions: true)
|
||||||
currentBirthdays = birthdays
|
currentBirthdays = birthdays
|
||||||
} else {
|
} else {
|
||||||
starsMode = .starsGifting(birthdays: nil, hasActions: true)
|
mode = .starsGifting(birthdays: nil, hasActions: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contactOptions: Signal<[ContactListAdditionalOption], NoError>
|
let contactOptions: Signal<[ContactListAdditionalOption], NoError>
|
||||||
@ -2247,15 +2316,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
var sendMessageImpl: ((EnginePeer) -> Void)?
|
var sendMessageImpl: ((EnginePeer) -> Void)?
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let controller: ViewController
|
|
||||||
// if case .stars = source {
|
|
||||||
// let options = Promise<[StarsGiftOption]>()
|
|
||||||
// options.set(context.engine.payments.starsGiftOptions(peerId: nil))
|
|
||||||
let options = Promise<[PremiumGiftCodeOption]>()
|
let options = Promise<[PremiumGiftCodeOption]>()
|
||||||
options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil))
|
options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil))
|
||||||
let contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(
|
let controller = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(
|
||||||
context: context,
|
context: context,
|
||||||
mode: starsMode,
|
mode: mode,
|
||||||
autoDismiss: false,
|
autoDismiss: false,
|
||||||
title: { strings in return "Gift Premium or Stars" },
|
title: { strings in return "Gift Premium or Stars" },
|
||||||
options: contactOptions,
|
options: contactOptions,
|
||||||
@ -2266,102 +2331,19 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
sendMessageImpl?(peer)
|
sendMessageImpl?(peer)
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get())
|
let _ = combineLatest(queue: Queue.mainQueue(), controller.result, options.get())
|
||||||
.startStandalone(next: { [weak contactsController] result, options in
|
.startStandalone(next: { [weak controller] result, options in
|
||||||
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext {
|
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext {
|
||||||
let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
|
let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
|
||||||
let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions)
|
let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions)
|
||||||
giftController.navigationPresentation = .modal
|
giftController.navigationPresentation = .modal
|
||||||
contactsController?.push(giftController)
|
controller?.push(giftController)
|
||||||
|
|
||||||
// completion?([peer.id])
|
|
||||||
|
|
||||||
if case .chatList = source, let _ = currentBirthdays {
|
if case .chatList = source, let _ = currentBirthdays {
|
||||||
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone()
|
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
controller = contactsController
|
|
||||||
// } else {
|
|
||||||
// let options = Promise<[PremiumGiftCodeOption]>()
|
|
||||||
// options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil))
|
|
||||||
// let contactsController = context.sharedContext.makeContactMultiselectionController(
|
|
||||||
// ContactMultiselectionControllerParams(
|
|
||||||
// context: context,
|
|
||||||
// mode: mode,
|
|
||||||
// options: contactOptions,
|
|
||||||
// isPeerEnabled: { peer in
|
|
||||||
// if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) {
|
|
||||||
// return true
|
|
||||||
// } else {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// limit: limit,
|
|
||||||
// reachedLimit: { limit in
|
|
||||||
// reachedLimitImpl?(limit)
|
|
||||||
// },
|
|
||||||
// openProfile: { peer in
|
|
||||||
// openProfileImpl?(peer)
|
|
||||||
// },
|
|
||||||
// sendMessage: { peer in
|
|
||||||
// sendMessageImpl?(peer)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get())
|
|
||||||
// .startStandalone(next: { [weak contactsController] result, options in
|
|
||||||
// guard let controller = contactsController else {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// var peerIds: [PeerId] = []
|
|
||||||
// if case let .result(peerIdsValue, _) = result {
|
|
||||||
// peerIds = peerIdsValue.compactMap({ peerId in
|
|
||||||
// if case let .peer(peerId) = peerId {
|
|
||||||
// return peerId
|
|
||||||
// } else {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// guard !peerIds.isEmpty else {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let mappedOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
|
|
||||||
// var pushImpl: ((ViewController) -> Void)?
|
|
||||||
// var filterImpl: (() -> Void)?
|
|
||||||
// let giftController = PremiumGiftScreen(context: context, peerIds: peerIds, options: mappedOptions, source: source, pushController: { c in
|
|
||||||
// pushImpl?(c)
|
|
||||||
// }, completion: {
|
|
||||||
// filterImpl?()
|
|
||||||
//
|
|
||||||
// if case .chatList = source, let _ = currentBirthdays {
|
|
||||||
// let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// pushImpl = { [weak giftController] c in
|
|
||||||
// giftController?.push(c)
|
|
||||||
// }
|
|
||||||
// filterImpl = { [weak giftController] in
|
|
||||||
// if let navigationController = giftController?.navigationController as? NavigationController {
|
|
||||||
// var controllers = navigationController.viewControllers
|
|
||||||
// controllers = controllers.filter { !($0 is ContactMultiselectionController) && !($0 is PremiumGiftScreen) }
|
|
||||||
// navigationController.setViewControllers(controllers, animated: true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// controller.push(giftController)
|
|
||||||
// })
|
|
||||||
// controller = contactsController
|
|
||||||
// }
|
|
||||||
|
|
||||||
// reachedLimitImpl = { [weak controller] limit in
|
|
||||||
// guard let controller else {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// HapticFeedback().error()
|
|
||||||
// controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Premium_Gift_ContactSelection_MaximumReached("\(limit)").string, timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
|
||||||
// }
|
|
||||||
|
|
||||||
sendMessageImpl = { [weak self, weak controller] peer in
|
sendMessageImpl = { [weak self, weak controller] peer in
|
||||||
guard let self, let controller, let navigationController = controller.navigationController as? NavigationController else {
|
guard let self, let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||||
@ -2864,6 +2846,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||||||
var hintGroupInCommon: PeerId?
|
var hintGroupInCommon: PeerId?
|
||||||
var forumTopicThread: ChatReplyThreadMessage?
|
var forumTopicThread: ChatReplyThreadMessage?
|
||||||
var isMyProfile = false
|
var isMyProfile = false
|
||||||
|
var switchToGifts = false
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case let .nearbyPeer(distance):
|
case let .nearbyPeer(distance):
|
||||||
@ -2880,10 +2863,13 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||||||
forumTopicThread = thread
|
forumTopicThread = thread
|
||||||
case .myProfile:
|
case .myProfile:
|
||||||
isMyProfile = true
|
isMyProfile = true
|
||||||
|
case .myProfileGifts:
|
||||||
|
isMyProfile = true
|
||||||
|
switchToGifts = true
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread)
|
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, switchToGifts: switchToGifts)
|
||||||
} else if peer is TelegramSecretChat {
|
} else if peer is TelegramSecretChat {
|
||||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
|
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,8 @@ public enum DeviceModel: CaseIterable, Equatable {
|
|||||||
.iPhone15Plus,
|
.iPhone15Plus,
|
||||||
.iPhone15Pro,
|
.iPhone15Pro,
|
||||||
.iPhone15ProMax,
|
.iPhone15ProMax,
|
||||||
|
.iPhone16,
|
||||||
|
.iPhone16Plus,
|
||||||
.iPhone16Pro,
|
.iPhone16Pro,
|
||||||
.iPhone16ProMax
|
.iPhone16ProMax
|
||||||
]
|
]
|
||||||
@ -118,6 +120,8 @@ public enum DeviceModel: CaseIterable, Equatable {
|
|||||||
case iPhone15Pro
|
case iPhone15Pro
|
||||||
case iPhone15ProMax
|
case iPhone15ProMax
|
||||||
|
|
||||||
|
case iPhone16
|
||||||
|
case iPhone16Plus
|
||||||
case iPhone16Pro
|
case iPhone16Pro
|
||||||
case iPhone16ProMax
|
case iPhone16ProMax
|
||||||
|
|
||||||
@ -223,6 +227,10 @@ public enum DeviceModel: CaseIterable, Equatable {
|
|||||||
return ["iPhone16,1"]
|
return ["iPhone16,1"]
|
||||||
case .iPhone15ProMax:
|
case .iPhone15ProMax:
|
||||||
return ["iPhone16,2"]
|
return ["iPhone16,2"]
|
||||||
|
case .iPhone16:
|
||||||
|
return ["iPhone17,3"]
|
||||||
|
case .iPhone16Plus:
|
||||||
|
return ["iPhone17,4"]
|
||||||
case .iPhone16Pro:
|
case .iPhone16Pro:
|
||||||
return ["iPhone17,1"]
|
return ["iPhone17,1"]
|
||||||
case .iPhone16ProMax:
|
case .iPhone16ProMax:
|
||||||
@ -332,6 +340,10 @@ public enum DeviceModel: CaseIterable, Equatable {
|
|||||||
return "iPhone 15 Pro"
|
return "iPhone 15 Pro"
|
||||||
case .iPhone15ProMax:
|
case .iPhone15ProMax:
|
||||||
return "iPhone 15 Pro Max"
|
return "iPhone 15 Pro Max"
|
||||||
|
case .iPhone16:
|
||||||
|
return "iPhone 16"
|
||||||
|
case .iPhone16Plus:
|
||||||
|
return "iPhone 16 Plus"
|
||||||
case .iPhone16Pro:
|
case .iPhone16Pro:
|
||||||
return "iPhone 16 Pro"
|
return "iPhone 16 Pro"
|
||||||
case .iPhone16ProMax:
|
case .iPhone16ProMax:
|
||||||
|
|||||||
@ -48,7 +48,7 @@ CONFIGURE_FLAGS="--enable-cross-compile --disable-programs \
|
|||||||
--enable-libvpx \
|
--enable-libvpx \
|
||||||
--enable-audiotoolbox \
|
--enable-audiotoolbox \
|
||||||
--enable-bsf=aac_adtstoasc,vp9_superframe,h264_mp4toannexb \
|
--enable-bsf=aac_adtstoasc,vp9_superframe,h264_mp4toannexb \
|
||||||
--enable-decoder=h264,libvpx_vp9,hevc,libopus,mp3,aac,flac,alac_at,pcm_s16le,pcm_s24le,gsm_ms_at \
|
--enable-decoder=h264,libvpx_vp9,hevc,libopus,mp3,aac,flac,alac_at,pcm_s16le,pcm_s24le,pcm_f32le,gsm_ms_at \
|
||||||
--enable-encoder=libvpx_vp9,aac_at \
|
--enable-encoder=libvpx_vp9,aac_at \
|
||||||
--enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska,mpegts \
|
--enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska,mpegts \
|
||||||
--enable-parser=aac,h264,mp3,libopus \
|
--enable-parser=aac,h264,mp3,libopus \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user