Merge commit 'f199fe9641e29a7e1061cf559c434dffbf6fc49f'

This commit is contained in:
Isaac 2024-04-22 23:18:45 +04:00
commit 2a95160bf5
37 changed files with 409 additions and 201 deletions

View File

@ -12067,10 +12067,13 @@ Sorry for the inconvenience.";
"Login.EnterPhraseHint" = "You can copy and paste the phrase here.";
"Login.BackToWord" = "Back to entering the word";
"Login.BackToPhrase" = "Back to entering the phrase";
"Login.BackToCode" = "Back to entering the code";
"Login.WrongPhraseError" = "Incorrect, please try again.";
"Login.Paste" = "Paste";
"Login.ReturnToWord" = "Return to entering the word";
"Login.ReturnToPhrase" = "Return to entering the phrase";
"Login.ReturnToCode" = "Return to entering the code";
"Map.LiveLocationPrivateNewDescription" = "Choose for how long **%@** will see your accurate location, including when the app is closed.";
@ -12110,3 +12113,24 @@ Sorry for the inconvenience.";
"ReportPeer.ReportReaction.Text" = "Are you sure you want to report reactions from this user?";
"ReportPeer.ReportReaction.BanAndReport" = "Ban and Report";
"ReportPeer.ReportReaction.Report" = "Report Reaction";
"StoryFeed.ViewAnonymously" = "View Anonymously";
"Channel.AdminLogFilter.EventsAdminRights" = "New Admin Rights";
"Channel.AdminLogFilter.EventsExceptions" = "New Exceptions";
"Channel.AdminLogFilter.EventsLeavingGroup" = "Members Left the Group";
"Channel.AdminLogFilter.EventsLeavingChannel" = "Subscribers Removed";
"Channel.AdminLogFilter.RecentActionsTitle" = "Recent Actions";
"Channel.AdminLogFilter.FilterActionsTypeTitle" = "FILTER ACTIONS BY TYPE";
"Channel.AdminLogFilter.FilterActionsAdminsTitle" = "FILTER ACTIONS BY ADMINS";
"Channel.AdminLogFilter.ShowAllAdminsActions" = "Show Actions by All Admins";
"Channel.AdminLogFilter.ApplyFilter" = "Apply Filter";
"Channel.AdminLogFilter.Section.MembersGroup" = "Members and Admins";
"Channel.AdminLogFilter.Section.MembersChannel" = "Subscribers and Admins";
"Channel.AdminLogFilter.Section.SettingsGroup" = "Group Settings";
"Channel.AdminLogFilter.Section.SettingsChannel" = "Channel Settings";
"Channel.AdminLogFilter.Section.Messages" = "Messages";
"Premium.Gift.ContactSelection.AddBirthday" = "Add Your Birthday";

View File

@ -93,7 +93,7 @@ public final class ContactMultiselectionControllerParams {
public let context: AccountContext
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let mode: ContactMultiselectionControllerMode
public let options: [ContactListAdditionalOption]
public let options: Signal<[ContactListAdditionalOption], NoError>
public let filters: [ContactListFilter]
public let onlyWriteable: Bool
public let isGroupInvitation: Bool
@ -105,7 +105,7 @@ public final class ContactMultiselectionControllerParams {
public let openProfile: ((EnginePeer) -> Void)?
public let sendMessage: ((EnginePeer) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.mode = mode

View File

@ -27,7 +27,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
var resetEmail: (() -> Void)?
var retryResetEmail: (() -> Void)?
var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?, Bool, Bool)?
var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?, SentAuthorizationCodeType?, Bool)?
var termsOfService: (UnauthorizedAccountTermsOfService, Bool)?
private let hapticFeedback = HapticFeedback()
@ -41,17 +41,8 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
}
}
var isWordOrPhrase: Bool {
if let type = self.data?.2 {
switch type {
case .word, .phrase:
return true
default:
return false
}
} else {
return false
}
var isPrevious: Bool {
return self.data?.6 ?? false
}
public init(presentationData: PresentationData, back: @escaping () -> Void) {
@ -141,12 +132,12 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
self?.present(c, in: .window(.root), with: a)
}
if let (number, email, codeType, nextType, timeout, hasPreviousCode, previousIsPhrase) = self.data {
if let (number, email, codeType, nextType, timeout, previousCodeType, isPrevious) = self.data {
var appleSignInAllowed = false
if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType {
appleSignInAllowed = appleSignInAllowedValue
}
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase)
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, previousCodeType: previousCodeType, isPrevious: isPrevious)
}
}
@ -194,10 +185,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
}
}
public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?, hasPreviousCode: Bool, previousIsPhrase: Bool) {
public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?, previousCodeType: SentAuthorizationCodeType?, isPrevious: Bool) {
self.termsOfService = termsOfService
if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout || self.data?.5 != hasPreviousCode || self.data?.6 != previousIsPhrase {
self.data = (number, email, codeType, nextType, timeout, hasPreviousCode, previousIsPhrase)
if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout || self.data?.5 != previousCodeType || self.data?.6 != isPrevious {
self.data = (number, email, codeType, nextType, timeout, previousCodeType, isPrevious)
var appleSignInAllowed = false
if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType {
@ -205,7 +196,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
}
if self.isNodeLoaded {
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase)
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, previousCodeType: previousCodeType, isPrevious: isPrevious)
self.requestLayout(transition: .immediate)
}
}

View File

@ -59,8 +59,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
private var layoutArguments: (ContainerViewLayout, CGFloat)?
private var appleSignInAllowed = false
private var hasPreviousCode = false
private var previousIsPhrase = false
private var previousCodeType: SentAuthorizationCodeType?
private var isPrevious = false
var phoneNumber: String = "" {
didSet {
@ -403,12 +404,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.codeInputView.text = ""
}
func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool, hasPreviousCode: Bool, previousIsPhrase: Bool) {
func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool, previousCodeType: SentAuthorizationCodeType?, isPrevious: Bool) {
self.codeType = codeType
self.phoneNumber = number.replacingOccurrences(of: " ", with: "\u{00A0}").replacingOccurrences(of: "-", with: "\u{2011}")
self.email = email
self.hasPreviousCode = hasPreviousCode
self.previousIsPhrase = previousIsPhrase
self.previousCodeType = previousCodeType
self.isPrevious = isPrevious
var appleSignInAllowed = appleSignInAllowed
if #available(iOS 13.0, *) {
@ -441,7 +442,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if let strongSelf = self {
if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 {
strongSelf.currentTimeoutTime = currentTimeoutTime - 1
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemSecondaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor)
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, previousCodeType: isPrevious ? previousCodeType : nil, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemSecondaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor)
strongSelf.nextOptionTitleNode.attributedText = nextOptionText
strongSelf.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
strongSelf.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
@ -482,7 +483,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.countdownDisposable.set(nil)
}
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemSecondaryTextColor, accentColor: self.theme.list.itemAccentColor)
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, previousCodeType: isPrevious ? previousCodeType : nil, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemSecondaryTextColor, accentColor: self.theme.list.itemAccentColor)
self.nextOptionTitleNode.attributedText = nextOptionText
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
@ -492,12 +493,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.nextOptionButtonNode.accessibilityTraits = [.button, .notEnabled]
}
switch codeType {
case .word, .phrase:
self.nextOptionArrowNode.isHidden = !hasPreviousCode
default:
self.nextOptionArrowNode.isHidden = true
}
self.nextOptionArrowNode.isHidden = previousCodeType == nil || !isPrevious
if let layoutArguments = self.layoutArguments {
self.containerLayoutUpdated(layoutArguments.0, navigationBarHeight: layoutArguments.1, transition: .immediate)
@ -807,7 +803,38 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if let codeType = self.codeType {
let yOffset: CGFloat = layout.size.width > 320.0 ? 18.0 : 5.0
if case .phrase = codeType {
if let previousCodeType = self.previousCodeType, !self.isPrevious {
self.hintButtonNode.alpha = 1.0
self.hintButtonNode.isUserInteractionEnabled = true
let actionTitle: String
switch previousCodeType {
case .word:
actionTitle = self.strings.Login_BackToWord
case .phrase:
actionTitle = self.strings.Login_BackToPhrase
default:
actionTitle = self.strings.Login_BackToCode
}
self.hintTextNode.attributedText = NSAttributedString(string: actionTitle, font: Font.regular(13.0), textColor: self.theme.list.itemAccentColor, paragraphAlignment: .center)
let originY: CGFloat
if !self.codeInputView.isHidden {
originY = self.codeInputView.frame.maxY + 6.0
} else {
originY = self.textField.frame.maxY
}
let hintTextSize = self.hintTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude))
transition.updateFrame(node: self.hintButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - hintTextSize.width) / 2.0), y: originY + yOffset), size: hintTextSize))
self.hintTextNode.frame = CGRect(origin: .zero, size: hintTextSize)
if let icon = self.hintArrowNode.image {
self.hintArrowNode.frame = CGRect(origin: CGPoint(x: self.hintTextNode.frame.minX - icon.size.width - 5.0, y: self.hintTextNode.frame.midY - icon.size.height / 2.0), size: icon.size)
}
self.hintArrowNode.isHidden = false
} else if case .phrase = codeType {
if self.errorTextNode.alpha.isZero {
self.hintButtonNode.alpha = 1.0
}
@ -827,20 +854,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.hintButtonNode.alpha = 0.0
self.hintButtonNode.isUserInteractionEnabled = false
self.hintArrowNode.isHidden = true
} else if self.hasPreviousCode {
self.hintButtonNode.alpha = 1.0
self.hintButtonNode.isUserInteractionEnabled = true
self.hintTextNode.attributedText = NSAttributedString(string: self.previousIsPhrase ? self.strings.Login_BackToPhrase : self.strings.Login_BackToWord, font: Font.regular(13.0), textColor: self.theme.list.itemAccentColor, paragraphAlignment: .center)
let hintTextSize = self.hintTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude))
transition.updateFrame(node: self.hintButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - hintTextSize.width) / 2.0), y: self.codeInputView.frame.maxY + yOffset + 6.0), size: hintTextSize))
self.hintTextNode.frame = CGRect(origin: .zero, size: hintTextSize)
if let icon = self.hintArrowNode.image {
self.hintArrowNode.frame = CGRect(origin: CGPoint(x: self.hintTextNode.frame.minX - icon.size.width - 5.0, y: self.hintTextNode.frame.midY - icon.size.height / 2.0), size: icon.size)
}
self.hintArrowNode.isHidden = false
let pasteSize = self.pasteButton.measure(layout.size)
let pasteButtonSize = CGSize(width: pasteSize.width + 16.0, height: 24.0)
transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: layout.size.width - 40.0 - pasteButtonSize.width, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize))
} else {
self.hintButtonNode.alpha = 0.0
self.hintButtonNode.isUserInteractionEnabled = false
@ -1002,7 +1019,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var updated = textField.text ?? ""
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
return checkValidity(text: updated) && !updated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
if updated.isEmpty {
return true
} else {
return checkValidity(text: updated) && !updated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
func checkValidity(text: String) -> Bool {
@ -1019,7 +1040,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if let startsWith, !text.isEmpty {
let firstWord = text.components(separatedBy: " ").first ?? ""
if !firstWord.isEmpty && !startsWith.hasPrefix(firstWord) {
if self.errorTextNode.alpha.isZero {
if self.errorTextNode.alpha.isZero, text.count < 4 {
self.animateError(text: self.strings.Login_WrongPhraseError)
}
return false

View File

@ -312,7 +312,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
return controller
}
private func codeEntryController(number: String, phoneCodeHash: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, hasPreviousCode: Bool, previousIsPhrase: Bool, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController {
private func codeEntryController(number: String, phoneCodeHash: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, previousCodeType: SentAuthorizationCodeType?, isPrevious: Bool, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController {
var currentController: AuthorizationSequenceCodeEntryController?
for c in self.viewControllers {
if let c = c as? AuthorizationSequenceCodeEntryController {
@ -584,14 +584,9 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
}
controller.requestNextOption = { [weak self, weak controller] in
if let strongSelf = self {
switch type {
case .word, .phrase:
if hasPreviousCode {
strongSelf.actionDisposable.set(togglePreviousCodeEntry(account: strongSelf.account).start())
return
}
default:
break
if previousCodeType != nil && isPrevious {
strongSelf.actionDisposable.set(togglePreviousCodeEntry(account: strongSelf.account).start())
return
}
if nextType == nil {
@ -677,7 +672,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
strongSelf.sharedContext.applicationBindings.openUrl(url)
}
}
controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase)
controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService, previousCodeType: previousCodeType, isPrevious: isPrevious)
return controller
}
@ -1237,25 +1232,19 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
controllers.append(self.emailSetupController(number: number, appleSignInAllowed: self.appleSignInAllowed))
}
if let previousCodeEntry, case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _, _, _) = previousCodeEntry, usePrevious {
var previousIsPhrase = false
if case .phrase = type {
previousIsPhrase = true
}
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, hasPreviousCode: true, previousIsPhrase: previousIsPhrase, termsOfService: nil))
if let previousCodeEntry, case let .confirmationCodeEntry(number, previousType, phoneCodeHash, timeout, nextType, _, _, _) = previousCodeEntry, usePrevious {
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: previousType, nextType: nextType, timeout: timeout, previousCodeType: type, isPrevious: true, termsOfService: nil))
isGoingBack = true
} else {
var previousIsPhrase = false
var previousCodeType: SentAuthorizationCodeType?
if let previousCodeEntry, case let .confirmationCodeEntry(_, type, _, _, _, _, _, _) = previousCodeEntry {
if case .phrase = type {
previousIsPhrase = true
}
previousCodeType = type
}
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, hasPreviousCode: previousCodeEntry != nil, previousIsPhrase: previousIsPhrase, termsOfService: nil))
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, previousCodeType: previousCodeType, isPrevious: false, termsOfService: nil))
}
}
if isGoingBack, let currentLastController = self.viewControllers.last as? AuthorizationSequenceCodeEntryController, !currentLastController.isWordOrPhrase {
if isGoingBack, let currentLastController = self.viewControllers.last as? AuthorizationSequenceCodeEntryController, !currentLastController.isPrevious {
var tempControllers = controllers
tempControllers.append(currentLastController)
self.setViewControllers(tempControllers, animated: false)

View File

@ -56,18 +56,20 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph
}
}
public func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, returnToCode: Bool = false, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) {
public func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, previousCodeType: SentAuthorizationCodeType? = nil, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) {
let font = Font.regular(16.0)
switch currentType {
case .word, .phrase:
if returnToCode {
if let previousCodeType {
switch previousCodeType {
case .word:
return (NSAttributedString(string: strings.Login_ReturnToWord, font: font, textColor: accentColor, paragraphAlignment: .center), true)
case .phrase:
return (NSAttributedString(string: strings.Login_ReturnToPhrase, font: font, textColor: accentColor, paragraphAlignment: .center), true)
default:
return (NSAttributedString(string: strings.Login_ReturnToCode, font: font, textColor: accentColor, paragraphAlignment: .center), true)
}
default:
break
}
if let nextType = nextType, let timeout = timeout, timeout > 0 {
let minutes = timeout / 60
let seconds = timeout % 60

View File

@ -3082,6 +3082,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
})))
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryFeed_ViewAnonymously, icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: self.context.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, a in
// a(.default)
//
// guard let self else {
// return
// }
// if self.context.isPremium {
//// self.sendMessageContext.requestStealthMode(view: self)
// } else {
//// self.presentStealthModeUpgradeScreen()
// }
// })))
let hideText: String
if self.location == .chatList(groupId: .archive) {
hideText = self.presentationData.strings.StoryFeed_ContextUnarchive

View File

@ -760,7 +760,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
selectedChats: Set(filterData.includePeers.peers),
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: allFilters
)), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
)), filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
if count >= premiumLimit {
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: min(premiumLimit, count), action: {
return true
@ -913,7 +913,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
selectedChats: Set(filterData.excludePeers),
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: allFilters
)), options: [], filters: [], alwaysEnabled: true, limit: 100))
)), filters: [], alwaysEnabled: true, limit: 100))
controller.navigationPresentation = .modal
let _ = (controller.result
|> take(1)

View File

@ -1770,25 +1770,32 @@ public final class ContactListNode: ASDisplayNode {
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
isEmpty = true
}
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers.map { $0.peer }, topPeersPresentation: displayTopPeers, interaction: interaction)
let previous = previousEntries.swap(entries)
let previousSelection = previousSelectionState.swap(selectionState)
let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds)
var hadPermissionInfo = false
var previousOptionsCount = 0
if let previous = previous {
for entry in previous {
if case .permissionInfo = entry {
hadPermissionInfo = true
break
}
if case .option = entry {
previousOptionsCount += 1
}
}
}
var hasPermissionInfo = false
var optionsCount = 0
for entry in entries {
if case .permissionInfo = entry {
hasPermissionInfo = true
break
}
if case .option = entry {
optionsCount += 1
}
}
@ -1799,6 +1806,8 @@ public final class ContactListNode: ASDisplayNode {
animation = .insertion
} else if hadPermissionInfo != hasPermissionInfo {
animation = .insertion
} else if optionsCount < previousOptionsCount {
animation = .insertion
} else {
animation = .none
}

View File

@ -38,6 +38,7 @@ private struct LocationViewTransaction {
let updates: [ListViewUpdateItem]
let gotTravelTimes: Bool
let count: Int
let animated: Bool
}
private enum LocationViewEntryId: Hashable {
@ -197,14 +198,14 @@ private enum LocationViewEntry: Comparable, Identifiable {
}
}
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool) -> LocationViewTransaction {
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool, animated: Bool) -> LocationViewTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count)
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count, animated: animated)
}
enum LocationViewLocation: Equatable {
@ -573,7 +574,27 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let previousState = previousState.swap(state)
let previousHadTravelTimes = previousHadTravelTimes.swap(!travelTimes.isEmpty)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes)
var animated = false
var previousActionsCount = 0
var actionsCount = 0
if let previousEntries {
for entry in previousEntries {
if case .toggleLiveLocation = entry {
previousActionsCount += 1
}
}
}
for entry in entries {
if case .toggleLiveLocation = entry {
actionsCount += 1
}
}
if actionsCount < previousActionsCount {
animated = true
}
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes, animated: animated)
strongSelf.enqueueTransition(transition)
strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false)
@ -787,7 +808,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
scrollToItem = nil
}
let options = ListViewDeleteAndInsertOptions()
var options = ListViewDeleteAndInsertOptions()
if transition.animated {
options.insert(.AnimateInsertion)
}
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
})
}

View File

@ -518,7 +518,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
)
|> deliverOnMainQueue).start(next: { chatPeer, exportedInvitation, members in
let disabledIds = members?.compactMap({$0.peer.id}) ?? []
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: [], filters: [.excludeSelf, .disable(disabledIds)], onlyWriteable: true, isGroupInvitation: true))
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), filters: [.excludeSelf, .disable(disabledIds)], onlyWriteable: true, isGroupInvitation: true))
addMembersDisposable.set((
contactsController.result

View File

@ -2276,7 +2276,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
nextImpl = { [weak controller] in
if let controller = controller {
if case .initialSetup = mode {
let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, options: [], onlyWriteable: true))
let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, onlyWriteable: true))
(controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true)
let _ = (selectionController.result
|> deliverOnMainQueue).start(next: { [weak selectionController] result in

View File

@ -108,7 +108,7 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll
codeController.openFragment = { url in
context.sharedContext.applicationBindings.openUrl(url)
}
codeController.updateData(number: formatPhoneNumber(context: context, number: phoneNumber), email: nil, codeType: next.type, nextType: nil, timeout: next.timeout, termsOfService: nil, hasPreviousCode: false, previousIsPhrase: false)
codeController.updateData(number: formatPhoneNumber(context: context, number: phoneNumber), email: nil, codeType: next.type, nextType: nil, timeout: next.timeout, termsOfService: nil, previousCodeType: nil, isPrevious: false)
dismissImpl = { [weak codeController] in
codeController?.dismiss()
}

View File

@ -315,7 +315,6 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32,
chatListFilters: nil,
displayAutoremoveTimeout: true
)),
options: [],
filters: [.excludeSelf],
isPeerEnabled: { peer in
var canManage = false

View File

@ -1446,7 +1446,7 @@ public func privacyAndSecurityController(
emailChangeCompletion(codeController)
}))
}
codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil, hasPreviousCode: false, previousIsPhrase: false)
codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil, previousCodeType: nil, isPrevious: false)
pushControllerImpl?(codeController, true)
dismissCodeControllerImpl = { [weak codeController] in
codeController?.dismiss()

View File

@ -1141,7 +1141,7 @@ public func selectivePrivacySettingsController(
onlyUsers: false,
disableChannels: true,
disableBots: false
)), options: [], filters: [.excludeSelf]))
)), filters: [.excludeSelf]))
addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] result in

View File

@ -355,7 +355,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
onlyUsers: false,
disableChannels: true,
disableBots: false
)), options: [], alwaysEnabled: true))
)), alwaysEnabled: true))
addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] result in

View File

@ -1401,11 +1401,11 @@ private func monetizationEntries(
isCreator = true
}
entries.append(.adsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_BalanceTitle))
entries.append(.adsBalance(presentationData.theme, data, isCreator && data.availableBalance > 0, monetizationConfiguration.withdrawalAvailable, diamond))
entries.append(.adsBalance(presentationData.theme, data, isCreator && data.balances.availableBalance > 0, monetizationConfiguration.withdrawalAvailable, diamond))
if isCreator {
let withdrawalInfoText: String
if data.availableBalance == 0 {
if data.balances.availableBalance == 0 {
withdrawalInfoText = presentationData.strings.Monetization_Balance_ZeroInfo
} else if monetizationConfiguration.withdrawalAvailable {
withdrawalInfoText = presentationData.strings.Monetization_Balance_AvailableInfo
@ -1579,7 +1579,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
let revenueContext = RevenueStatsContext(postbox: context.account.postbox, network: context.account.network, peerId: peerId)
let revenueContext = RevenueStatsContext(account: context.account, peerId: peerId)
let revenueState = Promise<RevenueStatsContextState?>()
revenueState.set(.single(nil) |> then(revenueContext.state |> map(Optional.init)))
@ -2127,11 +2127,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|> deliverOnMainQueue).start(error: { error in
let controller = revenueWithdrawalController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, initialError: error, present: { c, _ in
presentImpl?(c)
}, completion: { [weak revenueContext] url in
}, completion: { url in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
revenueContext?.reload()
})
presentImpl?(controller)
}))

View File

@ -158,13 +158,13 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode {
let integralFont = Font.with(size: 48.0, design: .round, weight: .semibold)
let fractionalFont = Font.with(size: 24.0, design: .round, weight: .semibold)
let cryptoValue = formatBalanceText(item.stats.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
let cryptoValue = formatBalanceText(item.stats.balances.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
let amountString = amountAttributedString(cryptoValue, integralFont: integralFont, fractionalFont: fractionalFont, color: item.presentationData.theme.list.itemPrimaryTextColor)
let (balanceLayout, balanceApply) = makeBalanceTextLayout(TextNodeLayoutArguments(attributedString: amountString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let value = item.stats.availableBalance == 0 ? "" : "\(formatUsdValue(item.stats.availableBalance, rate: item.stats.usdRate))"
let value = item.stats.balances.availableBalance == 0 ? "" : "\(formatUsdValue(item.stats.balances.availableBalance, rate: item.stats.usdRate))"
let (valueLayout, valueApply) = makeValueTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: value, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 13.0

View File

@ -732,9 +732,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
formatBalanceText(stats.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
formatBalanceText(stats.balances.availableBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_Overview_Available,
(stats.availableBalance == 0 ? "" : "\(formatUsdValue(stats.availableBalance, rate: stats.usdRate))", .generic),
(stats.balances.availableBalance == 0 ? "" : "\(formatUsdValue(stats.balances.availableBalance, rate: stats.usdRate))", .generic),
true
)
@ -742,9 +742,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
formatBalanceText(stats.currentBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
formatBalanceText(stats.balances.currentBalance, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_Overview_Current,
(stats.currentBalance == 0 ? "" : "\(formatUsdValue(stats.currentBalance, rate: stats.usdRate))", .generic),
(stats.balances.currentBalance == 0 ? "" : "\(formatUsdValue(stats.balances.currentBalance, rate: stats.usdRate))", .generic),
true
)
@ -752,9 +752,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
formatBalanceText(stats.overallRevenue, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
formatBalanceText(stats.balances.overallRevenue, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator),
item.presentationData.strings.Monetization_Overview_Total,
(stats.overallRevenue == 0 ? "" : "\(formatUsdValue(stats.overallRevenue, rate: stats.usdRate))", .generic),
(stats.balances.overallRevenue == 0 ? "" : "\(formatUsdValue(stats.balances.overallRevenue, rate: stats.usdRate))", .generic),
true
)

View File

@ -1362,7 +1362,7 @@ public extension Api {
return parser(reader)
}
else {
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
return nil
}
}

View File

@ -128,6 +128,7 @@ enum AccountStateMutationOperation {
case UpdateStorySentReaction(peerId: PeerId, id: Int32, reaction: Api.Reaction)
case UpdateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String)
case UpdateWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?)
case UpdateRevenueBalances(RevenueStats.Balances)
}
struct HoleFromPreviousState {
@ -524,7 +525,7 @@ struct AccountMutableState {
mutating func updatePeersNearby(_ peersNearby: [PeerNearby]) {
self.addOperation(.UpdatePeersNearby(peersNearby))
}
mutating func updateTheme(_ theme: TelegramTheme) {
self.addOperation(.UpdateTheme(theme))
}
@ -673,9 +674,13 @@ struct AccountMutableState {
self.addOperation(.UpdateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date, device: device, location: location))
}
mutating func updateRevenueBalances(_ balances: RevenueStats.Balances) {
self.addOperation(.UpdateRevenueBalances(balances))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances:
break
case let .AddMessages(messages, location):
for message in messages {
@ -819,6 +824,7 @@ struct AccountReplayedFinalState {
let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id]
let updateConfig: Bool
let isPremiumUpdated: Bool
let updatedRevenueBalances: RevenueStats.Balances?
}
struct AccountFinalStateEvents {
@ -845,12 +851,13 @@ struct AccountFinalStateEvents {
let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id]
let updateConfig: Bool
let isPremiumUpdated: Bool
let updatedRevenueBalances: RevenueStats.Balances?
var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !isPremiumUpdated
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances == nil
}
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false) {
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: RevenueStats.Balances? = nil) {
self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds
@ -874,6 +881,7 @@ struct AccountFinalStateEvents {
self.updatedOutgoingThreadReadStates = updatedOutgoingThreadReadStates
self.updateConfig = updateConfig
self.isPremiumUpdated = isPremiumUpdated
self.updatedRevenueBalances = updatedRevenueBalances
}
init(state: AccountReplayedFinalState) {
@ -900,6 +908,7 @@ struct AccountFinalStateEvents {
self.updatedOutgoingThreadReadStates = state.updatedOutgoingThreadReadStates
self.updateConfig = state.updateConfig
self.isPremiumUpdated = state.isPremiumUpdated
self.updatedRevenueBalances = state.updatedRevenueBalances
}
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -929,6 +938,6 @@ struct AccountFinalStateEvents {
let isPremiumUpdated = self.isPremiumUpdated || other.isPremiumUpdated
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated)
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances != nil ? self.updatedRevenueBalances : other.updatedRevenueBalances)
}
}

View File

@ -269,6 +269,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
case let .sentCode(sentCode):
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
let parsedType = SentAuthorizationCodeType(apiType: type)
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
@ -316,11 +317,16 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
switch parsedType {
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
}
}
}
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
return .sentCode(account)
}
@ -331,7 +337,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
}
}
}
var previousCodeEntry: UnauthorizedAccountStateContents?
if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents {
if let previousCodeEntryValue {
@ -341,11 +347,16 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
switch parsedType {
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
}
}
}
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
case let .sentCodeSuccess(authorization):
switch authorization {
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
@ -404,6 +415,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
return account.postbox.transaction { transaction -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
let parsedType = SentAuthorizationCodeType(apiType: type)
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
@ -451,11 +463,16 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
switch parsedType {
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
}
}
}
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
return .sentCode(account)
}
@ -476,7 +493,12 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
switch parsedType {
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
}
}
}
}
@ -498,17 +520,6 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
switch state.contents {
case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _):
if nextType != nil {
var previousCodeEntry: UnauthorizedAccountStateContents?
if let previousCodeEntryValue {
previousCodeEntry = previousCodeEntryValue
} else {
switch type {
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
}
}
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeRequestError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
@ -528,13 +539,31 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
case let .sentCode(_, newType, phoneCodeHash, nextType, codeTimeout):
let parsedType = SentAuthorizationCodeType(apiType: newType)
var previousCodeEntry: UnauthorizedAccountStateContents?
if let previousCodeEntryValue {
previousCodeEntry = previousCodeEntryValue
} else {
switch type {
case .word, .phrase:
previousCodeEntry = state.contents
default:
switch parsedType {
case .word, .phrase:
previousCodeEntry = state.contents
default:
break
}
}
}
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = newType {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
@ -567,7 +596,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
if success {
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
return .sentCode(account)
}
@ -580,7 +609,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|> map { _ -> Void in return Void() }
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
case .sentCodeSuccess:
break
}

View File

@ -1776,6 +1776,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
updatedState.updateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date ?? 0, device: device ?? "", location: location ?? "")
case let .updatePeerWallpaper(_, peer, wallpaper):
updatedState.updateWallpaper(peerId: peer.peerId, wallpaper: wallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) })
case let .updateBroadcastRevenueTransactions(balances):
updatedState.updateRevenueBalances(RevenueStats.Balances(apiRevenueBalances: balances))
default:
break
}
@ -3267,7 +3269,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddQuickReplyMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -3400,6 +3402,7 @@ func replayFinalState(
var deletedMessageIds: [DeletedMessageId] = []
var syncAttachMenuBots = false
var updateConfig = false
var updatedRevenueBalances: RevenueStats.Balances?
var holesFromPreviousStateMessageIds: [MessageId] = []
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
@ -4825,6 +4828,8 @@ func replayFinalState(
} else {
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: id.rawValue)
}
case let .UpdateRevenueBalances(balances):
updatedRevenueBalances = balances
}
}
@ -5319,5 +5324,5 @@ func replayFinalState(
}
}
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedReactionEvents: addedReactionEvents, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates, updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated)
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedReactionEvents: addedReactionEvents, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates, updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: updatedRevenueBalances)
}

View File

@ -44,6 +44,10 @@ private final class UpdatedPeersNearbySubscriberContext {
let subscribers = Bag<([PeerNearby]) -> Void>()
}
private final class UpdatedRevenueBalancesSubscriberContext {
let subscribers = Bag<(RevenueStats.Balances) -> Void>()
}
public enum DeletedMessageId: Hashable {
case global(Int32)
case messageId(MessageId)
@ -277,6 +281,7 @@ public final class AccountStateManager {
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext()
private let delayNotificatonsUntil = Atomic<Int32?>(value: nil)
private let appliedMaxMessageIdPromise = Promise<Int32?>(nil)
@ -1022,6 +1027,9 @@ public final class AccountStateManager {
if let updatedPeersNearby = events.updatedPeersNearby {
strongSelf.notifyUpdatedPeersNearby(updatedPeersNearby)
}
if let updatedRevenueBalances = events.updatedRevenueBalances {
strongSelf.notifyUpdatedRevenueBalances(updatedRevenueBalances)
}
if !events.updatedCalls.isEmpty {
for call in events.updatedCalls {
strongSelf.callSessionManager?.updateSession(call, completion: { _ in })
@ -1594,6 +1602,33 @@ public final class AccountStateManager {
}
}
public func updatedRevenueBalances() -> Signal<RevenueStats.Balances, NoError> {
let queue = self.queue
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
queue.async {
if let strongSelf = self {
let index = strongSelf.updatedRevenueBalancesContext.subscribers.add({ revenueBalances in
subscriber.putNext(revenueBalances)
})
disposable.set(ActionDisposable {
if let strongSelf = self {
strongSelf.updatedRevenueBalancesContext.subscribers.remove(index)
}
})
}
}
return disposable
}
}
private func notifyUpdatedRevenueBalances(_ updatedRevenueBalances: RevenueStats.Balances) {
for subscriber in self.updatedRevenueBalancesContext.subscribers.copyItems() {
subscriber(updatedRevenueBalances)
}
}
func notifyDeletedMessages(messageIds: [MessageId]) {
self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) })
}
@ -1881,6 +1916,12 @@ public final class AccountStateManager {
}
}
public func updatedRevenueBalances() -> Signal<RevenueStats.Balances, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.updatedRevenueBalances().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
func addCustomOperation<T, E>(_ f: Signal<T, E>) -> Signal<T, E> {
return self.impl.signalWith { impl, subscriber in
return impl.addCustomOperation(f).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)

View File

@ -162,7 +162,7 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network,
case let .emojiGroups(hash, groups):
let categories = EmojiSearchCategories(
hash: hash,
groups: groups.map { item -> EmojiSearchCategories.Group in
groups: groups.compactMap { item -> EmojiSearchCategories.Group? in
switch item {
case let .emojiGroup(title, iconEmojiId, emoticons):
return EmojiSearchCategories.Group(

View File

@ -5,19 +5,21 @@ import TelegramApi
import MtProtoKit
public struct RevenueStats: Equatable {
public struct Balances: Equatable {
public let currentBalance: Int64
public let availableBalance: Int64
public let overallRevenue: Int64
}
public let topHoursGraph: StatsGraph
public let revenueGraph: StatsGraph
public let currentBalance: Int64
public let availableBalance: Int64
public let overallRevenue: Int64
public let balances: Balances
public let usdRate: Double
init(topHoursGraph: StatsGraph, revenueGraph: StatsGraph, currentBalance: Int64, availableBalance: Int64, overallRevenue: Int64, usdRate: Double) {
init(topHoursGraph: StatsGraph, revenueGraph: StatsGraph, balances: Balances, usdRate: Double) {
self.topHoursGraph = topHoursGraph
self.revenueGraph = revenueGraph
self.currentBalance = currentBalance
self.availableBalance = availableBalance
self.overallRevenue = overallRevenue
self.balances = balances
self.usdRate = usdRate
}
@ -28,13 +30,7 @@ public struct RevenueStats: Equatable {
if lhs.revenueGraph != rhs.revenueGraph {
return false
}
if lhs.currentBalance != rhs.currentBalance {
return false
}
if lhs.availableBalance != rhs.availableBalance {
return false
}
if lhs.overallRevenue != rhs.overallRevenue {
if lhs.balances != rhs.balances {
return false
}
if lhs.usdRate != rhs.usdRate {
@ -44,14 +40,31 @@ public struct RevenueStats: Equatable {
}
}
public extension RevenueStats {
func withUpdated(balances: RevenueStats.Balances) -> RevenueStats {
return RevenueStats(
topHoursGraph: self.topHoursGraph,
revenueGraph: self.revenueGraph,
balances: balances,
usdRate: self.usdRate
)
}
}
extension RevenueStats {
init(apiRevenueStats: Api.stats.BroadcastRevenueStats, peerId: PeerId) {
switch apiRevenueStats {
case let .broadcastRevenueStats(topHoursGraph, revenueGraph, balances, usdRate):
switch balances {
case let .broadcastRevenueBalances(currentBalance, availableBalance, overallRevenue):
self.init(topHoursGraph: StatsGraph(apiStatsGraph: topHoursGraph), revenueGraph: StatsGraph(apiStatsGraph: revenueGraph), currentBalance: currentBalance, availableBalance: availableBalance, overallRevenue: overallRevenue, usdRate: usdRate)
}
self.init(topHoursGraph: StatsGraph(apiStatsGraph: topHoursGraph), revenueGraph: StatsGraph(apiStatsGraph: revenueGraph), balances: RevenueStats.Balances(apiRevenueBalances: balances), usdRate: usdRate)
}
}
}
extension RevenueStats.Balances {
init(apiRevenueBalances: Api.BroadcastRevenueBalances) {
switch apiRevenueBalances {
case let .broadcastRevenueBalances(currentBalance, availableBalance, overallRevenue):
self.init(currentBalance: currentBalance, availableBalance: availableBalance, overallRevenue: overallRevenue)
}
}
}
@ -85,8 +98,7 @@ private func requestRevenueStats(postbox: Postbox, network: Network, peerId: Pee
}
private final class RevenueStatsContextImpl {
private let postbox: Postbox
private let network: Network
private let account: Account
private let peerId: PeerId
private var _state: RevenueStatsContextState {
@ -103,11 +115,10 @@ private final class RevenueStatsContextImpl {
private let disposable = MetaDisposable()
init(postbox: Postbox, network: Network, peerId: PeerId) {
init(account: Account, peerId: PeerId) {
assert(Queue.mainQueue().isCurrent())
self.postbox = postbox
self.network = network
self.account = account
self.peerId = peerId
self._state = RevenueStatsContextState(stats: nil)
self._statePromise.set(.single(self._state))
@ -123,7 +134,22 @@ private final class RevenueStatsContextImpl {
fileprivate func load() {
assert(Queue.mainQueue().isCurrent())
self.disposable.set((requestRevenueStats(postbox: self.postbox, network: self.network, peerId: self.peerId)
let account = self.account
let signal = requestRevenueStats(postbox: self.account.postbox, network: self.account.network, peerId: self.peerId)
|> mapToSignal { initial -> Signal<RevenueStats?, NoError> in
guard let initial else {
return .single(nil)
}
return .single(initial)
|> then(
account.stateManager.updatedRevenueBalances()
|> map { balances in
return initial.withUpdated(balances: balances)
}
)
}
self.disposable.set((signal
|> deliverOnMainQueue).start(next: { [weak self] stats in
if let strongSelf = self {
strongSelf._state = RevenueStatsContextState(stats: stats)
@ -134,7 +160,7 @@ private final class RevenueStatsContextImpl {
func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
if let token = graph.token {
return requestGraph(postbox: self.postbox, network: self.network, peerId: self.peerId, token: token, x: x)
return requestGraph(postbox: self.account.postbox, network: self.account.network, peerId: self.peerId, token: token, x: x)
} else {
return .single(nil)
}
@ -156,9 +182,9 @@ public final class RevenueStatsContext {
}
}
public init(postbox: Postbox, network: Network, peerId: PeerId) {
public init(account: Account, peerId: PeerId) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return RevenueStatsContextImpl(postbox: postbox, network: network, peerId: peerId)
return RevenueStatsContextImpl(account: account, peerId: peerId)
})
}
@ -187,6 +213,7 @@ private final class RevenueStatsTransactionsContextImpl {
private let account: Account
private let peerId: EnginePeer.Id
private let disposable = MetaDisposable()
private var updateDisposable: Disposable?
private var isLoadingMore: Bool = false
private var hasLoadedOnce: Bool = false
private var canLoadMore: Bool = true
@ -204,13 +231,21 @@ private final class RevenueStatsTransactionsContextImpl {
self.count = 0
self.loadMore()
self.updateDisposable = (account.stateManager.updatedRevenueBalances()
|> deliverOn(self.queue)).startStrict(next: { [weak self] _ in
self?.reload()
})
}
deinit {
self.disposable.dispose()
self.updateDisposable?.dispose()
}
func reload() {
self.lastOffset = nil
self.loadMore()
}

View File

@ -31,16 +31,16 @@ private enum MembersActionType: Hashable, CaseIterable {
case newMembers
case leftMembers
func title(strings: PresentationStrings) -> String {
func title(isGroup: Bool, strings: PresentationStrings) -> String {
switch self {
case .newAdminRights:
return "New Admin Rights"
return strings.Channel_AdminLogFilter_EventsAdminRights
case .newExceptions:
return "New Exceptions"
return strings.Channel_AdminLogFilter_EventsExceptions
case .newMembers:
return "New Members"
return isGroup ? strings.Channel_AdminLogFilter_EventsNewMembers : strings.Channel_AdminLogFilter_EventsNewSubscribers
case .leftMembers:
return "Members left the Group"
return isGroup ? strings.Channel_AdminLogFilter_EventsLeavingGroup : strings.Channel_AdminLogFilter_EventsLeavingChannel
}
}
@ -152,7 +152,7 @@ private enum ActionType: Hashable {
func title(isGroup: Bool, strings: PresentationStrings) -> String {
switch self {
case let .members(value):
return value.title(strings: strings)
return value.title(isGroup: isGroup, strings: strings)
case let .settings(value):
return value.title(isGroup: isGroup, strings: strings)
case let .messages(value):
@ -515,15 +515,15 @@ private final class RecentActionsSettingsSheetComponent: Component {
case .members:
totalCount = MembersActionType.allCases.count
selectedCount = self.selectedMembersActions.count
title = isGroup ? "Members and Admins" : "Subscribers and Admins"
title = isGroup ? environment.strings.Channel_AdminLogFilter_Section_MembersGroup : environment.strings.Channel_AdminLogFilter_Section_MembersChannel
case .settings:
totalCount = SettingsActionType.allCases.count
selectedCount = self.selectedSettingsActions.count
title = isGroup ? "Group Settings" : "Channel Settings"
title = isGroup ? environment.strings.Channel_AdminLogFilter_Section_SettingsGroup : environment.strings.Channel_AdminLogFilter_Section_SettingsChannel
case .messages:
totalCount = MessagesActionType.allCases.count
selectedCount = self.selectedMessagesActions.count
title = "Messages"
title = environment.strings.Channel_AdminLogFilter_Section_Messages
}
let itemTitle: AnyComponent<Empty> = AnyComponent(HStack([
@ -575,7 +575,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
theme: environment.theme,
title: itemTitle,
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
isSelected: selectedCount != 0,
isSelected: selectedCount == totalCount,
toggle: {
toggleAction()
}
@ -684,8 +684,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
)))
}
//TODO:localize
let titleString: String = "Recent Actions"
let titleString: String = environment.strings.Channel_AdminLogFilter_RecentActionsTitle
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
@ -722,7 +721,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "FILTER ACTIONS BY TYPE",
string: environment.strings.Channel_AdminLogFilter_FilterActionsTypeTitle,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -790,7 +789,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Show Actions by All Admins",
string: environment.strings.Channel_AdminLogFilter_ShowAllAdminsActions,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
@ -798,7 +797,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
))),
], alignment: .left, spacing: 2.0)),
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
isSelected: !self.selectedAdmins.isEmpty,
isSelected: self.selectedAdmins.count == component.adminPeers.count,
toggle: {
allAdminsToggleAction()
}
@ -821,7 +820,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "FILTER ACTIONS BY ADMINS",
string: environment.strings.Channel_AdminLogFilter_FilterActionsAdminsTitle,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -856,7 +855,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(ButtonTextContentComponent(
text: "Apply Filter",
text: environment.strings.Channel_AdminLogFilter_ApplyFilter,
badge: 0,
textColor: environment.theme.list.itemCheckColors.foregroundColor,
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,

View File

@ -2244,13 +2244,15 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
private let deletedMessagesDisplayedLimit = 5
func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentationData: ChatPresentationData, expandedDeletedMessages: Set<EngineMessage.Id>) -> [ChatRecentActionsEntry] {
var result: [ChatRecentActionsEntry] = []
var deleteMessageEntries: [ChannelAdminEventLogEntry] = []
func appendCurrentDeleteEntries() {
if !deleteMessageEntries.isEmpty, let lastEntry = deleteMessageEntries.last, let lastMessageId = lastEntry.event.action.messageId {
let isExpandable = deleteMessageEntries.count > 10
let isExpandable = deleteMessageEntries.count > deletedMessagesDisplayedLimit
let isExpanded = expandedDeletedMessages.contains(lastMessageId) || !isExpandable
let isGroup = deleteMessageEntries.count > 1

View File

@ -13097,7 +13097,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
}, clearHighlightAutomatically: true))
}
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true))
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: .single(options), filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true))
contactsController.navigationPresentation = .modal
confirmationImpl = { [weak contactsController] peerId in

View File

@ -384,7 +384,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: nil,
onlyUsers: true
)), options: [], filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
)), filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
}))
controller.navigationPresentation = .modal

View File

@ -242,7 +242,7 @@ final class BusinessRecipientListScreenComponent: Component {
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: nil,
onlyUsers: true
)), options: [], filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
)), filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
}))
controller.navigationPresentation = .modal

View File

@ -4156,7 +4156,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case .user = peerType, maxQuantity > 1 {
let presentationData = self.presentationData
var reachedLimitImpl: ((Int32) -> Void)?
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, options: [], isPeerEnabled: { peer in
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, isPeerEnabled: { peer in
if case let .user(user) = peer, user.botInfo == nil {
return true
} else {

View File

@ -124,7 +124,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
self.contactsNode.openCreateNewGroup = { [weak self] in
if let strongSelf = self {
let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, options: [], onlyWriteable: true))
let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, onlyWriteable: true))
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)

View File

@ -81,7 +81,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
private var limitsConfiguration: LimitsConfiguration?
private var limitsConfigurationDisposable: Disposable?
private var initialPeersDisposable: Disposable?
private let options: [ContactListAdditionalOption]
private let options: Signal<[ContactListAdditionalOption], NoError>
private let filters: [ContactListFilter]
private let onlyWriteable: Bool
private let isGroupInvitation: Bool

View File

@ -82,7 +82,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
private let onlyWriteable: Bool
private let isGroupInvitation: Bool
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
self.navigationBar = navigationBar
self.context = context
@ -235,7 +235,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
} else {
displayTopPeers = .none
}
let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState())
let presentation: Signal<ContactListPresentation, NoError> = options
|> map { options in
return .natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)
}
let contactListNode = ContactListNode(context: context, presentation: presentation, filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState())
self.contentNode = .contacts(contactListNode)
if !selectedPeers.isEmpty {

View File

@ -2165,16 +2165,26 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mode = .premiumGifting(birthdays: nil, selectToday: false)
}
var contactOptions: [ContactListAdditionalOption] = []
let contactOptions: Signal<[ContactListAdditionalOption], NoError>
if currentBirthdays != nil || "".isEmpty {
contactOptions = [ContactListAdditionalOption(
title: "Add Your Birthday",
icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!),
action: {
presentBirthdayPickerImpl?()
},
clearHighlightAutomatically: true
)]
contactOptions = 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
} else {
contactOptions = .single([])
}
var openProfileImpl: ((EnginePeer) -> Void)?