Various improvements

This commit is contained in:
Ilya Laktyushin 2023-10-18 03:34:47 +04:00
parent bcbcc94b58
commit ec0f8028aa
23 changed files with 199 additions and 79 deletions

View File

@ -663,9 +663,10 @@ public final class ContactSelectionControllerParams {
public let displayDeviceContacts: Bool
public let displayCallIcons: Bool
public let multipleSelection: Bool
public let requirePhoneNumbers: Bool
public let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) }) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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<Bool, NoError> = { _ in .single(true) }) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.autoDismiss = autoDismiss
@ -674,6 +675,7 @@ public final class ContactSelectionControllerParams {
self.displayDeviceContacts = displayDeviceContacts
self.displayCallIcons = displayCallIcons
self.multipleSelection = multipleSelection
self.requirePhoneNumbers = requirePhoneNumbers
self.confirmation = confirmation
}
}

View File

@ -72,6 +72,7 @@ public enum ContactMultiselectionControllerMode {
}
public enum ContactListFilter {
case excludeWithoutPhoneNumbers
case excludeSelf
case exclude([EnginePeer.Id])
case disable([EnginePeer.Id])

View File

@ -113,7 +113,7 @@ private final class ContentNode: ASDisplayNode {
} else {
let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: peer.displayLetters, peerId: peer.id)
drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
})!
self.updateImage(image: image, size: size, spacing: spacing)
}
@ -346,7 +346,7 @@ public final class AnimatedAvatarSetView: UIView {
} else {
let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: peer.displayLetters, peerId: peer.id)
drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
})!
self.updateImage(image: image, size: size, spacing: spacing)
}

View File

@ -156,7 +156,7 @@ public func peerAvatarCompleteImage(postbox: Postbox, network: Network, peer: En
iconSignal = Signal { subscriber in
let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId, nameColor: peer.nameColor)
if blurred {
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
@ -346,7 +346,7 @@ public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: P
}
}
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: EnginePeer.Id) {
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: EnginePeer.Id, nameColor: PeerNameColor?) {
if round {
context.beginPath()
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
@ -365,7 +365,11 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool
if colorIndex == -1 {
colorsArray = AvatarNode.grayscaleColors.map(\.cgColor) as NSArray
} else {
colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count].map(\.cgColor) as NSArray
var index = colorIndex % AvatarNode.gradientColors.count
if let nameColor {
index = Int(nameColor.rawValue) % AvatarNode.gradientColors.count
}
colorsArray = AvatarNode.gradientColors[index].map(\.cgColor) as NSArray
}
var locations: [CGFloat] = [1.0, 0.0]

View File

@ -1085,15 +1085,18 @@ public final class ContactListNode: ASDisplayNode {
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
var excludeSelf = false
var requirePhoneNumbers = false
for filter in filters {
switch filter {
case .excludeSelf:
excludeSelf = true
existingPeerIds.insert(context.account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeSelf:
excludeSelf = true
existingPeerIds.insert(context.account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeWithoutPhoneNumbers:
requirePhoneNumbers = true
}
}
@ -1127,8 +1130,13 @@ public final class ContactListNode: ASDisplayNode {
}
for peer in remotePeers.0 {
let matches: Bool
if peer.peer is TelegramUser {
matches = true
if let user = peer.peer as? TelegramUser {
let phone = user.phone ?? ""
if requirePhoneNumbers && phone.isEmpty {
matches = false
} else {
matches = true
}
} else if searchGroups || searchChannels {
if peer.peer is TelegramGroup && searchGroups {
matches = true
@ -1157,8 +1165,13 @@ public final class ContactListNode: ASDisplayNode {
}
for peer in remotePeers.1 {
let matches: Bool
if peer.peer is TelegramUser {
matches = true
if let user = peer.peer as? TelegramUser {
let phone = user.phone ?? ""
if requirePhoneNumbers && phone.isEmpty {
matches = false
} else {
matches = true
}
} else if searchGroups || searchChannels {
if peer.peer is TelegramGroup {
matches = searchGroups
@ -1270,23 +1283,32 @@ public final class ContactListNode: ASDisplayNode {
}
var existingPeerIds = Set<EnginePeer.Id>()
var disabledPeerIds = Set<EnginePeer.Id>()
var requirePhoneNumbers = false
for filter in filters {
switch filter {
case .excludeSelf:
existingPeerIds.insert(context.account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeSelf:
existingPeerIds.insert(context.account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeWithoutPhoneNumbers:
requirePhoneNumbers = true
}
}
peers = peers.filter { contact in
switch contact {
case let .peer(peer, _, _):
return !existingPeerIds.contains(peer.id)
default:
return true
case let .peer(peer, _, _):
if requirePhoneNumbers, let user = peer as? TelegramUser {
let phone = user.phone ?? ""
if phone.isEmpty {
return false
}
}
return !existingPeerIds.contains(peer.id)
default:
return true
}
}

View File

@ -322,6 +322,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
var entries: [ContactListSearchEntry] = []
var existingPeerIds = Set<EnginePeer.Id>()
var disabledPeerIds = Set<EnginePeer.Id>()
var requirePhoneNumbers = false
for filter in filters {
switch filter {
case .excludeSelf:
@ -330,6 +331,8 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeWithoutPhoneNumbers:
requirePhoneNumbers = true
}
}
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
@ -338,6 +341,14 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
if existingPeerIds.contains(peer.id) {
continue
}
if case let .user(user) = peer, requirePhoneNumbers {
let phone = user.phone ?? ""
if phone.isEmpty {
continue
}
}
existingPeerIds.insert(peer.id)
var enabled = true
if onlyWriteable {
@ -354,6 +365,14 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
if !(peer.peer is TelegramUser) {
continue
}
if let user = peer.peer as? TelegramUser, requirePhoneNumbers {
let phone = user.phone ?? ""
if phone.isEmpty {
continue
}
}
if !existingPeerIds.contains(peer.peer.id) {
existingPeerIds.insert(peer.peer.id)
@ -373,6 +392,14 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
if !(peer.peer is TelegramUser) {
continue
}
if let user = peer.peer as? TelegramUser, requirePhoneNumbers {
let phone = user.phone ?? ""
if phone.isEmpty {
continue
}
}
if !existingPeerIds.contains(peer.peer.id) {
existingPeerIds.insert(peer.peer.id)

View File

@ -94,7 +94,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case timeHeader(PresentationTheme, String)
case timeExpiryDate(PresentationTheme, PresentationDateTimeFormat, Int32?, Bool)
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?)
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?, Int32?, Int32?)
case timeInfo(PresentationTheme, String)
case durationHeader(PresentationTheme, String)
@ -282,8 +282,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .timeCustomPicker(lhsTheme, lhsDateTimeFormat, lhsDate):
if case let .timeCustomPicker(rhsTheme, rhsDateTimeFormat, rhsDate) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate {
case let .timeCustomPicker(lhsTheme, lhsDateTimeFormat, lhsDate, lhsMinDate, lhsMaxDate):
if case let .timeCustomPicker(rhsTheme, rhsDateTimeFormat, rhsDate, rhsMinDate, rhsMaxDate) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate, lhsMinDate == rhsMinDate, lhsMaxDate == rhsMaxDate {
return true
} else {
return false
@ -450,8 +450,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
}
}
})
case let .timeCustomPicker(_, dateTimeFormat, date):
return ItemListDatePickerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, date: date, sectionId: self.section, style: .blocks, updated: { date in
case let .timeCustomPicker(_, dateTimeFormat, date, minDate, maxDate):
return ItemListDatePickerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, date: date, minDate: minDate, maxDate: maxDate, sectionId: self.section, style: .blocks, updated: { date in
arguments.updateState({ state in
var updatedState = state
updatedState.time = date
@ -499,7 +499,18 @@ private struct PremiumGiftProduct: Equatable {
}
}
private func createGiveawayControllerEntries(peerId: EnginePeer.Id, subject: CreateGiveawaySubject, state: CreateGiveawayControllerState, presentationData: PresentationData, locale: Locale, peers: [EnginePeer.Id: EnginePeer], products: [PremiumGiftProduct], defaultPrice: (Int64, NSDecimalNumber)) -> [CreateGiveawayEntry] {
private func createGiveawayControllerEntries(
peerId: EnginePeer.Id,
subject: CreateGiveawaySubject,
state: CreateGiveawayControllerState,
presentationData: PresentationData,
locale: Locale,
peers: [EnginePeer.Id: EnginePeer],
products: [PremiumGiftProduct],
defaultPrice: (Int64, NSDecimalNumber),
minDate: Int32,
maxDate: Int32
) -> [CreateGiveawayEntry] {
var entries: [CreateGiveawayEntry] = []
switch subject {
@ -568,7 +579,7 @@ private func createGiveawayControllerEntries(peerId: EnginePeer.Id, subject: Cre
entries.append(.timeHeader(presentationData.theme, "DATE WHEN GIVEAWAY ENDS".uppercased()))
entries.append(.timeExpiryDate(presentationData.theme, presentationData.dateTimeFormat, state.time, state.pickingTimeLimit))
if state.pickingTimeLimit {
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time))
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate))
}
entries.append(.timeInfo(presentationData.theme, "Choose when \(state.subscriptions) subscribers of your channel will be randomly selected to receive Telegram Premium."))
}
@ -657,7 +668,11 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
initialSubscriptions = 5
}
let expiryTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + 86400 * 5
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let expiryTime = currentTime + 86400 * 3
let minDate = currentTime + 60 * 10
let maxDate = currentTime + context.userLimits.maxGiveawayPeriodSeconds
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, channels: [], peers: [], countries: [], onlyNewEligible: false, time: expiryTime)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
@ -801,7 +816,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(""), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGiveawayControllerEntries(peerId: peerId, subject: subject, state: state, presentationData: presentationData, locale: locale, peers: peers, products: products, defaultPrice: defaultPrice), style: .blocks, emptyStateItem: nil, headerItem: headerItem, footerItem: footerItem, crossfadeState: false, animateChanges: animateChanges)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGiveawayControllerEntries(peerId: peerId, subject: subject, state: state, presentationData: presentationData, locale: locale, peers: peers, products: products, defaultPrice: defaultPrice, minDate: minDate, maxDate: maxDate), style: .blocks, emptyStateItem: nil, headerItem: headerItem, footerItem: footerItem, crossfadeState: false, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
}

View File

@ -27,7 +27,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
let context: AccountContext
let giftCode: PremiumGiftCodeInfo
let action: () -> Void
let cancel: () -> Void
let cancel: (Bool) -> Void
let openPeer: (EnginePeer) -> Void
let openMessage: (EngineMessage.Id) -> Void
let copyLink: (String) -> Void
@ -37,7 +37,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
context: AccountContext,
giftCode: PremiumGiftCodeInfo,
action: @escaping () -> Void,
cancel: @escaping () -> Void,
cancel: @escaping (Bool) -> Void,
openPeer: @escaping (EnginePeer) -> Void,
openMessage: @escaping (EngineMessage.Id) -> Void,
copyLink: @escaping (String) -> Void,
@ -149,7 +149,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
component: Button(
content: AnyComponent(Image(image: closeImage)),
action: { [weak component] in
component?.cancel()
component?.cancel(true)
}
),
availableSize: CGSize(width: 30.0, height: 30.0),
@ -240,8 +240,10 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
action: {
if let peer = fromPeer {
component.cancel()
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
}
)
@ -257,8 +259,10 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
action: {
if let peer = toPeer {
component.cancel()
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
}
)
@ -301,10 +305,12 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: giftReason, font: tableFont, textColor: giftCode.messageId != nil ? tableLinkColor : tableTextColor)))),
isEnabled: true,
action: {
component.cancel()
if let messageId = giftCode.messageId {
component.openMessage(messageId)
}
Queue.mainQueue().after(1.0) {
component.cancel(true)
}
}
)
)
@ -362,7 +368,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
iconPosition: .left,
action: {
if giftCode.isUsed {
component.cancel()
component.cancel(true)
} else {
component.action()
}
@ -425,7 +431,6 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
let context: AccountContext
let giftCode: PremiumGiftCodeInfo
let action: () -> Void
let cancel: () -> Void
let openPeer: (EnginePeer) -> Void
let openMessage: (EngineMessage.Id) -> Void
let copyLink: (String) -> Void
@ -435,7 +440,6 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
context: AccountContext,
giftCode: PremiumGiftCodeInfo,
action: @escaping () -> Void,
cancel: @escaping () -> Void,
openPeer: @escaping (EnginePeer) -> Void,
openMessage: @escaping (EngineMessage.Id) -> Void,
copyLink: @escaping (String) -> Void,
@ -444,7 +448,6 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
self.context = context
self.giftCode = giftCode
self.action = action
self.cancel = cancel
self.openPeer = openPeer
self.openMessage = openMessage
self.copyLink = copyLink
@ -475,7 +478,7 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
context: context.component.context,
giftCode: context.component.giftCode,
action: context.component.action,
cancel: {
cancel: { animate in
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
@ -535,7 +538,6 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
context: AccountContext,
giftCode: PremiumGiftCodeInfo,
forceDark: Bool = false,
cancel: @escaping () -> Void = {},
action: @escaping () -> Void,
openPeer: @escaping (EnginePeer) -> Void = { _ in },
openMessage: @escaping (EngineMessage.Id) -> Void = { _ in },
@ -544,7 +546,7 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
self.context = context
var copyLinkImpl: ((String) -> Void)?
super.init(context: context, component: PremiumGiftCodeSheetComponent(context: context, giftCode: giftCode, action: action, cancel: cancel, openPeer: openPeer, openMessage: openMessage, copyLink: { link in
super.init(context: context, component: PremiumGiftCodeSheetComponent(context: context, giftCode: giftCode, action: action, openPeer: openPeer, openMessage: openMessage, copyLink: { link in
copyLinkImpl?(link)
}, shareLink: shareLink), navigationBarAppearance: .none, statusBarStyle: .ignore, theme: forceDark ? .dark : .default)

View File

@ -49,7 +49,7 @@ public final class ChannelBoostStatus: Equatable {
}
func _internal_getChannelBoostStatus(account: Account, peerId: PeerId) -> Signal<ChannelBoostStatus?, NoError> {
return .single(nil)
return .single(ChannelBoostStatus(level: 0, boosts: 0, currentLevelBoosts: 0, nextLevelBoosts: nil, premiumAudience: nil, url: "", prepaidGiveaways: []))
// return account.postbox.transaction { transaction -> Api.InputPeer? in
// return transaction.getPeer(peerId).flatMap(apiInputPeer)
// }

View File

@ -24,6 +24,7 @@ public struct UserLimitsConfiguration: Equatable {
public let maxStoriesSuggestedReactions: Int32
public let maxGiveawayChannelsCount: Int32
public let maxGiveawayCountriesCount: Int32
public let maxGiveawayPeriodSeconds: Int32
public let minChannelNameColorLevel: Int32
public static var defaultValue: UserLimitsConfiguration {
@ -50,6 +51,7 @@ public struct UserLimitsConfiguration: Equatable {
maxStoriesSuggestedReactions: 1,
maxGiveawayChannelsCount: 10,
maxGiveawayCountriesCount: 10,
maxGiveawayPeriodSeconds: 86400 * 7,
minChannelNameColorLevel: 10
)
}
@ -77,6 +79,7 @@ public struct UserLimitsConfiguration: Equatable {
maxStoriesSuggestedReactions: Int32,
maxGiveawayChannelsCount: Int32,
maxGiveawayCountriesCount: Int32,
maxGiveawayPeriodSeconds: Int32,
minChannelNameColorLevel: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
@ -101,6 +104,7 @@ public struct UserLimitsConfiguration: Equatable {
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
self.minChannelNameColorLevel = minChannelNameColorLevel
}
}
@ -148,6 +152,7 @@ extension UserLimitsConfiguration {
self.maxStoriesSuggestedReactions = getValue("stories_suggested_reactions_limit", orElse: defaultValue.maxStoriesMonthlyCount)
self.maxGiveawayChannelsCount = getGeneralValue("giveaway_add_peers_max", orElse: defaultValue.maxGiveawayChannelsCount)
self.maxGiveawayCountriesCount = getGeneralValue("giveaway_countries_max", orElse: defaultValue.maxGiveawayCountriesCount)
self.maxGiveawayPeriodSeconds = getGeneralValue("giveaway_period_max", orElse: defaultValue.maxGiveawayPeriodSeconds)
self.minChannelNameColorLevel = getGeneralValue("channel_color_level_min", orElse: defaultValue.minChannelNameColorLevel)
}
}

View File

@ -58,6 +58,7 @@ public enum EngineConfiguration {
public let maxStoriesSuggestedReactions: Int32
public let maxGiveawayChannelsCount: Int32
public let maxGiveawayCountriesCount: Int32
public let maxGiveawayPeriodSeconds: Int32
public let minChannelNameColorLevel: Int32
public static var defaultValue: UserLimits {
@ -87,6 +88,7 @@ public enum EngineConfiguration {
maxStoriesSuggestedReactions: Int32,
maxGiveawayChannelsCount: Int32,
maxGiveawayCountriesCount: Int32,
maxGiveawayPeriodSeconds: Int32,
minChannelNameColorLevel: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
@ -111,6 +113,7 @@ public enum EngineConfiguration {
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
self.minChannelNameColorLevel = minChannelNameColorLevel
}
}
@ -171,6 +174,7 @@ public extension EngineConfiguration.UserLimits {
maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions,
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount,
maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount,
maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds,
minChannelNameColorLevel: userLimitsConfiguration.minChannelNameColorLevel
)
}

View File

@ -151,7 +151,7 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou
if let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarFont, letters: peer.displayLetters, peerId: peer.id)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarFont, letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
})?.withRenderingMode(.alwaysOriginal) {
avatarImage = image
}

View File

@ -771,6 +771,7 @@ private final class CameraScreenComponent: CombinedComponent {
)
context.add(frontFlash
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
.scale(1.5 - component.cameraState.flashTintSize * 0.5)
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)

View File

@ -1708,12 +1708,12 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
private enum PeerAvatarReference: Equatable {
case letters(PeerId, [String])
case letters(PeerId, PeerNameColor?, [String])
case image(PeerReference, TelegramMediaImageRepresentation)
var peerId: PeerId {
switch self {
case let .letters(value, _):
case let .letters(value, _, _):
return value
case let .image(value, _):
return value.id
@ -1726,7 +1726,7 @@ private extension PeerAvatarReference {
if let photo = peer.smallProfileImage, let peerReference = PeerReference(peer) {
self = .image(peerReference, photo)
} else {
self = .letters(peer.id, peer.displayLetters)
self = .letters(peer.id, peer.nameColor, peer.displayLetters)
}
}
}
@ -1885,9 +1885,9 @@ public final class MergedAvatarsNode: ASDisplayNode {
context.saveGState()
switch parameters.peers[i] {
case let .letters(peerId, letters):
case let .letters(peerId, nameColor, letters):
context.translateBy(x: currentX, y: 0.0)
drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId)
drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId, nameColor: nameColor)
context.translateBy(x: -currentX, y: 0.0)
case .image:
if let image = parameters.images[parameters.peers[i].peerId] {

View File

@ -11,6 +11,8 @@ public class ItemListDatePickerItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let dateTimeFormat: PresentationDateTimeFormat
let date: Int32?
let minDate: Int32?
let maxDate: Int32?
public let sectionId: ItemListSectionId
let style: ItemListStyle
let updated: ((Int32) -> Void)?
@ -20,6 +22,8 @@ public class ItemListDatePickerItem: ListViewItem, ItemListItem {
presentationData: ItemListPresentationData,
dateTimeFormat: PresentationDateTimeFormat,
date: Int32?,
minDate: Int32? = nil,
maxDate: Int32? = nil,
sectionId: ItemListSectionId,
style: ItemListStyle,
updated: ((Int32) -> Void)?,
@ -28,6 +32,8 @@ public class ItemListDatePickerItem: ListViewItem, ItemListItem {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.date = date
self.minDate = minDate
self.maxDate = maxDate
self.sectionId = sectionId
self.style = style
self.updated = updated
@ -223,7 +229,15 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.item?.updated?(Int32(date.timeIntervalSince1970))
}
datePickerNode.minimumDate = Date()
if let minDate = item.minDate {
datePickerNode.minimumDate = Date(timeIntervalSince1970: TimeInterval(minDate))
} else {
datePickerNode.minimumDate = Date()
}
if let maxDate = item.maxDate {
datePickerNode.maximumDate = Date(timeIntervalSince1970: TimeInterval(maxDate))
}
datePickerNode.date = item.date.flatMap { Date(timeIntervalSince1970: TimeInterval($0)) }
let datePickerSize = CGSize(width: width, height: contentSize.height)

View File

@ -164,6 +164,8 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
var currentBackgroundNode = self.backgroundNode
let currentItem = self.item
return { item, params, neighbors in
if currentBackgroundNode == nil {
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
@ -214,7 +216,9 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
Queue.mainQueue().after(0.01) {
apply(ListViewItemApply(isOnScreen: true))
}
})
}
} else {
@ -249,6 +253,15 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor {
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
strongSelf.view.addSubview(snapshot)
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
snapshot.removeFromSuperview()
})
}
}
strongSelf.messageNodes = nodes
var topOffset: CGFloat = 4.0
for node in nodes {

View File

@ -184,8 +184,10 @@ private final class PeerNameColorIconItemNode : ListViewItemNode {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate
if selected {
transition.updateTransformScale(node: self.fillNode, scale: 0.8)
transition.updateTransformScale(node: self.ringNode, scale: 1.0)
} else {
transition.updateTransformScale(node: self.fillNode, scale: 1.0)
transition.updateTransformScale(node: self.ringNode, scale: 0.99)
}
}

View File

@ -336,6 +336,7 @@ public func PeerNameColorScreen(
}
dismissImpl?()
} else {
HapticFeedback().error()
let controller = UndoOverlayController(
presentationData: presentationData,
content: .premiumPaywall(

View File

@ -10958,7 +10958,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else {
return
}
strongSelf.presentAttachmentPremiumGift()
strongSelf.presentAttachmentMenu(subject: .gift)
Queue.mainQueue().after(0.5) {
let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId).startStandalone()
}
@ -13389,10 +13389,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
func presentAttachmentPremiumGift() {
self.presentAttachmentMenu(subject: .gift)
}
enum AttachMenuSubject {
case `default`
case edit(mediaOptions: MessageMediaEditingOptions, mediaReference: AnyMediaReference)
@ -13720,7 +13716,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = currentLocationController.swap(controller)
})
case .contact:
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true))
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true, requirePhoneNumbers: true))
contactsController.presentScheduleTimePicker = { [weak self] completion in
if let strongSelf = self {
strongSelf.presentScheduleTimePicker(completion: completion)

View File

@ -1177,17 +1177,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
for media in message.media {
if let file = media as? TelegramMediaFile {
if file.isMusic {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveToFiles, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.saveMediaToFiles(message.id)
f(.default)
})))
if !isCopyProtected {
for media in message.media {
if let file = media as? TelegramMediaFile {
if file.isMusic {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveToFiles, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.saveMediaToFiles(message.id)
f(.default)
})))
}
break
}
break
}
}

View File

@ -37,6 +37,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
private let displayDeviceContacts: Bool
private let displayCallIcons: Bool
private let multipleSelection: Bool
private let requirePhoneNumbers: Bool
private var _ready = Promise<Bool>()
override var ready: Promise<Bool> {
@ -91,6 +92,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self.displayCallIcons = params.displayCallIcons
self.confirmation = params.confirmation
self.multipleSelection = params.multipleSelection
self.requirePhoneNumbers = params.requirePhoneNumbers
self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 }
@ -178,7 +180,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)
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._ready.set(self.contactsNode.contactListNode.ready)
self.contactsNode.navigationBar = self.navigationBar

View File

@ -23,6 +23,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
private let displayDeviceContacts: Bool
private let displayCallIcons: Bool
private let filters: [ContactListFilter]
let contactListNode: ContactListNode
private let dimNode: ASDisplayNode
@ -53,14 +54,20 @@ final class ContactSelectionControllerNode: ASDisplayNode {
var searchContainerNode: ContactsSearchContainerNode?
init(context: AccountContext, presentationData: PresentationData, options: [ContactListAdditionalOption], displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool) {
init(context: AccountContext, presentationData: PresentationData, options: [ContactListAdditionalOption], displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool, requirePhoneNumbers: Bool) {
self.context = context
self.presentationData = presentationData
self.displayDeviceContacts = displayDeviceContacts
self.displayCallIcons = displayCallIcons
var filters: [ContactListFilter] = [.excludeSelf]
if requirePhoneNumbers {
filters.append(.excludeWithoutPhoneNumbers)
}
self.filters = filters
var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false)), displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false)), filters: filters, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
contextActionImpl?(peer, node, gesture, nil)
} : nil, multipleSelection: multipleSelection)
@ -186,7 +193,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
categories.insert(.global)
}
let searchContainerNode = ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, addContact: nil, openPeer: { [weak self] peer in
let searchContainerNode = ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, filters: self.filters, addContact: nil, openPeer: { [weak self] peer in
if let strongSelf = self {
var updated = false
strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in

View File

@ -10304,7 +10304,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: inset, y: inset)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: displayLetters, peerId: primary.1.id)
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), font: avatarFont, letters: displayLetters, peerId: primary.1.id, nameColor: primary.1.nameColor)
})?.withRenderingMode(.alwaysOriginal)
if let image = image {
subscriber.putNext((image, image))