diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2401c55b05..2af213b33f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12542,3 +12542,7 @@ Sorry for the inconvenience."; "WebBrowser.LinkForwardTooltip.TwoChats.One" = "Link forwarded to **%@** and **%@**"; "WebBrowser.LinkForwardTooltip.ManyChats.One" = "Link forwarded to **%@** and %@ others"; "WebBrowser.LinkForwardTooltip.SavedMessages.One" = "Link forwarded to **Saved Messages**"; + +"Stars.Intro.StarsSent_1" = "%@ Star sent."; +"Stars.Intro.StarsSent_any" = "%@ Stars sent."; +"Stars.Intro.StarsSent.ViewChat" = "View Chat"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index e54fae0a28..8c04d63ab5 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -614,102 +614,6 @@ public enum ContactListActionItemIcon : Equatable { } } -public struct ContactListAdditionalOption: Equatable { - public let title: String - public let icon: ContactListActionItemIcon - public let action: () -> Void - public let clearHighlightAutomatically: Bool - - public init(title: String, icon: ContactListActionItemIcon, action: @escaping () -> Void, clearHighlightAutomatically: Bool = false) { - self.title = title - self.icon = icon - self.action = action - self.clearHighlightAutomatically = clearHighlightAutomatically - } - - public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool { - return lhs.title == rhs.title && lhs.icon == rhs.icon - } -} - -public enum ContactListPeerId: Hashable { - case peer(PeerId) - case deviceContact(DeviceContactStableId) -} - -public enum ContactListAction: Equatable { - case generic - case voiceCall - case videoCall - case more -} - -public enum ContactListPeer: Equatable { - case peer(peer: Peer, isGlobal: Bool, participantCount: Int32?) - case deviceContact(DeviceContactStableId, DeviceContactBasicData) - - public var id: ContactListPeerId { - switch self { - case let .peer(peer, _, _): - return .peer(peer.id) - case let .deviceContact(id, _): - return .deviceContact(id) - } - } - - public var indexName: PeerIndexNameRepresentation { - switch self { - case let .peer(peer, _, _): - return peer.indexName - case let .deviceContact(_, contact): - return .personName(first: contact.firstName, last: contact.lastName, addressNames: [], phoneNumber: "") - } - } - - public static func ==(lhs: ContactListPeer, rhs: ContactListPeer) -> Bool { - switch lhs { - case let .peer(lhsPeer, lhsIsGlobal, lhsParticipantCount): - if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer.isEqual(rhsPeer), lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount { - return true - } else { - return false - } - case let .deviceContact(id, contact): - if case .deviceContact(id, contact) = rhs { - return true - } else { - return false - } - } - } -} - -public final class ContactSelectionControllerParams { - public let context: AccountContext - public let updatedPresentationData: (initial: PresentationData, signal: Signal)? - public let autoDismiss: Bool - public let title: (PresentationStrings) -> String - public let options: [ContactListAdditionalOption] - public let displayDeviceContacts: Bool - public let displayCallIcons: Bool - public let multipleSelection: Bool - public let requirePhoneNumbers: Bool - public let confirmation: (ContactListPeer) -> Signal - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, requirePhoneNumbers: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal = { _ in .single(true) }) { - self.context = context - self.updatedPresentationData = updatedPresentationData - self.autoDismiss = autoDismiss - self.title = title - self.options = options - self.displayDeviceContacts = displayDeviceContacts - self.displayCallIcons = displayCallIcons - self.multipleSelection = multipleSelection - self.requirePhoneNumbers = requirePhoneNumbers - self.confirmation = confirmation - } -} - public enum ChatListSearchFilter: Equatable { case chats case topics diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index 19d4c5c60a..c16d295605 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -1,6 +1,9 @@ import Foundation import Display import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData public protocol ContactSelectionController: ViewController { var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get } @@ -10,3 +13,106 @@ public protocol ContactSelectionController: ViewController { func dismissSearch() } + +public enum ContactSelectionControllerMode { + case generic + case starsGifting(birthdays: [EnginePeer.Id: TelegramBirthday]?, hasActions: Bool) +} + +public struct ContactListAdditionalOption: Equatable { + public let title: String + public let icon: ContactListActionItemIcon + public let action: () -> Void + public let clearHighlightAutomatically: Bool + + public init(title: String, icon: ContactListActionItemIcon, action: @escaping () -> Void, clearHighlightAutomatically: Bool = false) { + self.title = title + self.icon = icon + self.action = action + self.clearHighlightAutomatically = clearHighlightAutomatically + } + + public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool { + return lhs.title == rhs.title && lhs.icon == rhs.icon + } +} + +public enum ContactListPeerId: Hashable { + case peer(PeerId) + case deviceContact(DeviceContactStableId) +} + +public enum ContactListAction: Equatable { + case generic + case voiceCall + case videoCall + case more +} + +public enum ContactListPeer: Equatable { + case peer(peer: Peer, isGlobal: Bool, participantCount: Int32?) + case deviceContact(DeviceContactStableId, DeviceContactBasicData) + + public var id: ContactListPeerId { + switch self { + case let .peer(peer, _, _): + return .peer(peer.id) + case let .deviceContact(id, _): + return .deviceContact(id) + } + } + + public var indexName: PeerIndexNameRepresentation { + switch self { + case let .peer(peer, _, _): + return peer.indexName + case let .deviceContact(_, contact): + return .personName(first: contact.firstName, last: contact.lastName, addressNames: [], phoneNumber: "") + } + } + + public static func ==(lhs: ContactListPeer, rhs: ContactListPeer) -> Bool { + switch lhs { + case let .peer(lhsPeer, lhsIsGlobal, lhsParticipantCount): + if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer.isEqual(rhsPeer), lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount { + return true + } else { + return false + } + case let .deviceContact(id, contact): + if case .deviceContact(id, contact) = rhs { + return true + } else { + return false + } + } + } +} + +public final class ContactSelectionControllerParams { + public let context: AccountContext + public let updatedPresentationData: (initial: PresentationData, signal: Signal)? + public let mode: ContactSelectionControllerMode + public let autoDismiss: Bool + public let title: (PresentationStrings) -> String + public let options: Signal<[ContactListAdditionalOption], NoError> + public let displayDeviceContacts: Bool + public let displayCallIcons: Bool + public let multipleSelection: Bool + public let requirePhoneNumbers: Bool + public let confirmation: (ContactListPeer) -> Signal + + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactSelectionControllerMode = .generic, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, requirePhoneNumbers: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal = { _ in .single(true) }) { + self.context = context + self.updatedPresentationData = updatedPresentationData + self.mode = mode + self.autoDismiss = autoDismiss + self.title = title + self.options = options + self.displayDeviceContacts = displayDeviceContacts + self.displayCallIcons = displayCallIcons + self.multipleSelection = multipleSelection + self.requirePhoneNumbers = requirePhoneNumbers + self.confirmation = confirmation + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 638f2d3f57..04df414332 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -857,8 +857,13 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { return } + if let navigationController = self.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is ContactSelectionController) } + navigationController.setViewControllers(controllers, animated: true) + } + Queue.mainQueue().after(2.0) { - //TODO:localize let presentationData = context.sharedContext.currentPresentationData.with { $0 } let resultController = UndoOverlayController( presentationData: presentationData, @@ -867,8 +872,8 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { scale: 0.066, colors: [:], title: nil, - text: "\(stars) Stars sent.", - customUndoText: "View Chat", + text: presentationData.strings.Stars_Intro_StarsSent(Int32(stars)), + customUndoText: presentationData.strings.Stars_Intro_StarsSent_ViewChat, timeout: nil ), elevatedLayout: false, diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 33c8ec4ec9..cabf9baa3e 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -17,6 +17,7 @@ import ChatSendMessageActionUI class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable { private let context: AccountContext + private let mode: ContactSelectionControllerMode private let autoDismiss: Bool fileprivate var contactsNode: ContactSelectionControllerNode { @@ -35,7 +36,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController private let index: PeerNameIndex = .lastNameFirst private let titleProducer: (PresentationStrings) -> String - private let options: [ContactListAdditionalOption] + private let options: Signal<[ContactListAdditionalOption], NoError> private let displayDeviceContacts: Bool private let displayCallIcons: Bool private let multipleSelection: Bool @@ -94,6 +95,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController init(_ params: ContactSelectionControllerParams) { self.context = params.context + self.mode = params.mode self.autoDismiss = params.autoDismiss self.titleProducer = params.title self.options = params.options @@ -207,7 +209,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } override func loadDisplayNode() { - self.displayNode = ContactSelectionControllerNode(context: self.context, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection, requirePhoneNumbers: self.requirePhoneNumbers) + self.displayNode = ContactSelectionControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection, requirePhoneNumbers: self.requirePhoneNumbers) self._ready.set(self.contactsNode.contactListNode.ready) self.contactsNode.navigationBar = self.navigationBar diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index cface4f8c9..bb954d35d6 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -55,7 +55,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { var searchContainerNode: ContactsSearchContainerNode? - init(context: AccountContext, presentationData: PresentationData, options: [ContactListAdditionalOption], displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool, requirePhoneNumbers: Bool) { + init(context: AccountContext, mode: ContactSelectionControllerMode, presentationData: PresentationData, options: Signal<[ContactListAdditionalOption], NoError>, displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool, requirePhoneNumbers: Bool) { self.context = context self.presentationData = presentationData self.displayDeviceContacts = displayDeviceContacts @@ -67,8 +67,50 @@ final class ContactSelectionControllerNode: ASDisplayNode { } self.filters = filters + let displayTopPeers: ContactListPresentation.TopPeers + if case let .starsGifting(birthdays, hasActions) = mode { + if let birthdays { + let today = Calendar(identifier: .gregorian).component(.day, from: Date()) + var sections: [(String, [EnginePeer.Id], Bool)] = [] + var todayPeers: [EnginePeer.Id] = [] + var yesterdayPeers: [EnginePeer.Id] = [] + var tomorrowPeers: [EnginePeer.Id] = [] + + for (peerId, birthday) in birthdays { + if birthday.day == today { + todayPeers.append(peerId) + } else if birthday.day == today - 1 || birthday.day > today + 5 { + yesterdayPeers.append(peerId) + } else if birthday.day == today + 1 || birthday.day < today + 5 { + tomorrowPeers.append(peerId) + } + } + + if !todayPeers.isEmpty { + sections.append((presentationData.strings.Premium_Gift_ContactSelection_BirthdayToday, todayPeers, hasActions)) + } + if !yesterdayPeers.isEmpty { + sections.append((presentationData.strings.Premium_Gift_ContactSelection_BirthdayYesterday, yesterdayPeers, hasActions)) + } + if !tomorrowPeers.isEmpty { + sections.append((presentationData.strings.Premium_Gift_ContactSelection_BirthdayTomorrow, tomorrowPeers, hasActions)) + } + + displayTopPeers = .custom(sections) + } else { + displayTopPeers = .recent + } + } else { + displayTopPeers = .none + } + + let presentation: Signal = options + |> map { options in + return .natural(options: options, includeChatList: false, topPeers: displayTopPeers) + } + var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? - self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: .none)), filters: filters, onlyWriteable: false, isGroupInvitation: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in + self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: presentation, filters: filters, onlyWriteable: false, isGroupInvitation: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in contextActionImpl?(peer, node, gesture, nil) } : nil, multipleSelection: multipleSelection) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3eda83a6f9..1afcc0ab6a 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2192,6 +2192,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { var reachedLimitImpl: ((Int32) -> Void)? var presentBirthdayPickerImpl: (() -> Void)? let mode: ContactMultiselectionControllerMode + var starsMode: ContactSelectionControllerMode = .generic var currentBirthdays: [EnginePeer.Id: TelegramBirthday]? if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty { mode = .premiumGifting(birthdays: birthdays, selectToday: true, hasActions: true) @@ -2201,6 +2202,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { currentBirthdays = birthdays } else if case let .stars(birthdays) = source { mode = .premiumGifting(birthdays: birthdays, selectToday: false, hasActions: false) + starsMode = .starsGifting(birthdays: birthdays, hasActions: false) currentBirthdays = birthdays } else { mode = .premiumGifting(birthdays: nil, selectToday: false, hasActions: true) @@ -2237,7 +2239,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { options.set(context.engine.payments.starsGiftOptions(peerId: nil)) let contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams( context: context, - title: { strings in return strings.Stars_Purchase_GiftStars } + mode: starsMode, + autoDismiss: false, + title: { strings in return strings.Stars_Purchase_GiftStars }, + options: contactOptions )) let _ = (contactsController.result |> deliverOnMainQueue).start(next: { result in