mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Stars subscriptions
This commit is contained in:
parent
dc68eab568
commit
bc454cfa93
@ -747,6 +747,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
nodeInteraction?.openPremiumGift(birthdays)
|
||||
case .reviewLogin:
|
||||
break
|
||||
case .starsSubscriptionLowBalance:
|
||||
break
|
||||
}
|
||||
case .hide:
|
||||
nodeInteraction?.dismissNotice(notice)
|
||||
@ -1085,6 +1087,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
nodeInteraction?.openPremiumGift(birthdays)
|
||||
case .reviewLogin:
|
||||
break
|
||||
case .starsSubscriptionLowBalance:
|
||||
break
|
||||
}
|
||||
case .hide:
|
||||
nodeInteraction?.dismissNotice(notice)
|
||||
|
@ -90,6 +90,7 @@ public enum ChatListNotice: Equatable {
|
||||
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
|
||||
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
||||
case premiumGrace
|
||||
case starsSubscriptionLowBalance
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
|
@ -262,6 +262,10 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
okButtonLayout = makeOkButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelConfirm, font: titleFont, textColor: item.theme.list.itemAccentColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
cancelButtonLayout = makeCancelButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelReject, font: titleFont, textColor: item.theme.list.itemDestructiveColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
case .starsSubscriptionLowBalance:
|
||||
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: "5 Stars needed for Astro Paws", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||
titleString = titleStringValue
|
||||
textString = NSAttributedString(string: "Insufficient funds to cover your subscription.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
}
|
||||
|
||||
var leftInset: CGFloat = sideInset
|
||||
|
@ -59,6 +59,7 @@ swift_library(
|
||||
"//submodules/QrCodeUI:QrCodeUI",
|
||||
"//submodules/PromptUI",
|
||||
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -17,6 +17,7 @@ import ContextUI
|
||||
import TelegramStringFormatting
|
||||
import UndoUI
|
||||
import ItemListDatePickerItem
|
||||
import TextFormat
|
||||
|
||||
private final class InviteLinkEditControllerArguments {
|
||||
let context: AccountContext
|
||||
@ -36,6 +37,7 @@ private final class InviteLinkEditControllerArguments {
|
||||
|
||||
private enum InviteLinksEditSection: Int32 {
|
||||
case title
|
||||
case subscriptionFee
|
||||
case requestApproval
|
||||
case time
|
||||
case usage
|
||||
@ -75,18 +77,23 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
case title(PresentationTheme, String, String)
|
||||
case titleInfo(PresentationTheme, String)
|
||||
|
||||
case requestApproval(PresentationTheme, String, Bool)
|
||||
|
||||
case subscriptionFeeToggle(PresentationTheme, String, Bool, Bool)
|
||||
case subscriptionFee(PresentationTheme, String, Bool, Int64?)
|
||||
case subscriptionFeeInfo(PresentationTheme, String)
|
||||
|
||||
case requestApproval(PresentationTheme, String, Bool, Bool)
|
||||
case requestApprovalInfo(PresentationTheme, String)
|
||||
|
||||
case timeHeader(PresentationTheme, String)
|
||||
case timePicker(PresentationTheme, InviteLinkTimeLimit)
|
||||
case timeExpiryDate(PresentationTheme, PresentationDateTimeFormat, Int32?, Bool)
|
||||
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?, Bool, Bool)
|
||||
case timePicker(PresentationTheme, InviteLinkTimeLimit, Bool)
|
||||
case timeExpiryDate(PresentationTheme, PresentationDateTimeFormat, Int32?, Bool, Bool)
|
||||
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?, Bool, Bool, Bool)
|
||||
case timeInfo(PresentationTheme, String)
|
||||
|
||||
case usageHeader(PresentationTheme, String)
|
||||
case usagePicker(PresentationTheme, PresentationDateTimeFormat, InviteLinkUsageLimit)
|
||||
case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool)
|
||||
case usagePicker(PresentationTheme, PresentationDateTimeFormat, InviteLinkUsageLimit, Bool)
|
||||
case usageCustomPicker(PresentationTheme, Int32?, Bool, Bool, Bool)
|
||||
case usageInfo(PresentationTheme, String)
|
||||
|
||||
case revoke(PresentationTheme, String)
|
||||
@ -95,6 +102,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case .titleHeader, .title, .titleInfo:
|
||||
return InviteLinksEditSection.title.rawValue
|
||||
case .subscriptionFeeToggle, .subscriptionFee, .subscriptionFeeInfo:
|
||||
return InviteLinksEditSection.subscriptionFee.rawValue
|
||||
case .requestApproval, .requestApprovalInfo:
|
||||
return InviteLinksEditSection.requestApproval.rawValue
|
||||
case .timeHeader, .timePicker, .timeExpiryDate, .timeCustomPicker, .timeInfo:
|
||||
@ -114,30 +123,36 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return 1
|
||||
case .titleInfo:
|
||||
return 2
|
||||
case .requestApproval:
|
||||
case .subscriptionFeeToggle:
|
||||
return 3
|
||||
case .requestApprovalInfo:
|
||||
case .subscriptionFee:
|
||||
return 4
|
||||
case .timeHeader:
|
||||
case .subscriptionFeeInfo:
|
||||
return 5
|
||||
case .timePicker:
|
||||
case .requestApproval:
|
||||
return 6
|
||||
case .timeExpiryDate:
|
||||
case .requestApprovalInfo:
|
||||
return 7
|
||||
case .timeCustomPicker:
|
||||
case .timeHeader:
|
||||
return 8
|
||||
case .timeInfo:
|
||||
case .timePicker:
|
||||
return 9
|
||||
case .usageHeader:
|
||||
case .timeExpiryDate:
|
||||
return 10
|
||||
case .usagePicker:
|
||||
case .timeCustomPicker:
|
||||
return 11
|
||||
case .usageCustomPicker:
|
||||
case .timeInfo:
|
||||
return 12
|
||||
case .usageInfo:
|
||||
case .usageHeader:
|
||||
return 13
|
||||
case .revoke:
|
||||
case .usagePicker:
|
||||
return 14
|
||||
case .usageCustomPicker:
|
||||
return 15
|
||||
case .usageInfo:
|
||||
return 16
|
||||
case .revoke:
|
||||
return 17
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,8 +176,26 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .requestApproval(lhsTheme, lhsText, lhsValue):
|
||||
if case let .requestApproval(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
case let .subscriptionFeeToggle(lhsTheme, lhsText, lhsValue, lhsEnabled):
|
||||
if case let .subscriptionFeeToggle(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionFee(lhsTheme, lhsText, lhsValue, lhsEnabled):
|
||||
if case let .subscriptionFee(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionFeeInfo(lhsTheme, lhsText):
|
||||
if case let .subscriptionFeeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .requestApproval(lhsTheme, lhsText, lhsValue, lhsEnabled):
|
||||
if case let .requestApproval(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -179,20 +212,20 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .timePicker(lhsTheme, lhsValue):
|
||||
if case let .timePicker(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
|
||||
case let .timePicker(lhsTheme, lhsValue, lhsEnabled):
|
||||
if case let .timePicker(rhsTheme, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .timeExpiryDate(lhsTheme, lhsDateTimeFormat, lhsDate, lhsActive):
|
||||
if case let .timeExpiryDate(rhsTheme, rhsDateTimeFormat, rhsDate, rhsActive) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate, lhsActive == rhsActive {
|
||||
case let .timeExpiryDate(lhsTheme, lhsDateTimeFormat, lhsDate, lhsActive, lhsEnabled):
|
||||
if case let .timeExpiryDate(rhsTheme, rhsDateTimeFormat, rhsDate, rhsActive, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate, lhsActive == rhsActive, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .timeCustomPicker(lhsTheme, lhsDateTimeFormat, lhsDate, lhsDisplayingDateSelection, lhsDisplayingTimeSelection):
|
||||
if case let .timeCustomPicker(rhsTheme, rhsDateTimeFormat, rhsDate, rhsDisplayingDateSelection, rhsDisplayingTimeSelection) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate, lhsDisplayingDateSelection == rhsDisplayingDateSelection, lhsDisplayingTimeSelection == rhsDisplayingTimeSelection {
|
||||
case let .timeCustomPicker(lhsTheme, lhsDateTimeFormat, lhsDate, lhsDisplayingDateSelection, lhsDisplayingTimeSelection, lhsEnabled):
|
||||
if case let .timeCustomPicker(rhsTheme, rhsDateTimeFormat, rhsDate, rhsDisplayingDateSelection, rhsDisplayingTimeSelection, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate, lhsDisplayingDateSelection == rhsDisplayingDateSelection, lhsDisplayingTimeSelection == rhsDisplayingTimeSelection, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -209,14 +242,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .usagePicker(lhsTheme, lhsDateTimeFormat, lhsValue):
|
||||
if case let .usagePicker(rhsTheme, rhsDateTimeFormat, rhsValue) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsValue == rhsValue {
|
||||
case let .usagePicker(lhsTheme, lhsDateTimeFormat, lhsValue, lhsEnabled):
|
||||
if case let .usagePicker(rhsTheme, rhsDateTimeFormat, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused, lhsCustomValue):
|
||||
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused, rhsCustomValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused, lhsCustomValue == rhsCustomValue {
|
||||
case let .usageCustomPicker(lhsTheme, lhsValue, lhsFocused, lhsCustomValue, lhsEnabled):
|
||||
if case let .usageCustomPicker(rhsTheme, rhsValue, rhsFocused, rhsCustomValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsFocused == rhsFocused, lhsCustomValue == rhsCustomValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -246,7 +279,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
case let .titleHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .title(_, placeholder, value):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, maxLength: 32, sectionId: self.section, textUpdated: { value in
|
||||
return ItemListSingleLineInputItem(context: arguments.context, presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, maxLength: 32, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.title = value
|
||||
@ -255,8 +288,41 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
}, action: {})
|
||||
case let .titleInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .requestApproval(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
|
||||
case let .subscriptionFeeToggle(_, text, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.subscriptionEnabled = value
|
||||
if value {
|
||||
updatedState.requestApproval = false
|
||||
} else {
|
||||
updatedState.subscriptionFee = nil
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
case let .subscriptionFee(_, placeholder, enabled, value):
|
||||
let title = NSMutableAttributedString(string: "⭐️", font: Font.semibold(18.0), textColor: .white)
|
||||
if let range = title.string.range(of: "⭐️") {
|
||||
title.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: title.string))
|
||||
title.addAttribute(.baselineOffset, value: -1.0, range: NSRange(range, in: title.string))
|
||||
}
|
||||
return ItemListSingleLineInputItem(context: arguments.context, presentationData: presentationData, title: title, text: value.flatMap { "\($0)" } ?? "", placeholder: placeholder, type: .number, spacing: 3.0, enabled: enabled, sectionId: self.section, textUpdated: { text in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
if let value = Int64(text) {
|
||||
updatedState.subscriptionFee = value
|
||||
} else {
|
||||
updatedState.subscriptionFee = nil
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
}, action: {})
|
||||
case let .subscriptionFeeInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .requestApproval(_, text, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.requestApproval = value
|
||||
@ -267,8 +333,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .timeHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .timePicker(_, value):
|
||||
return ItemListInviteLinkTimeLimitItem(theme: presentationData.theme, strings: presentationData.strings, value: value, enabled: true, sectionId: self.section, updated: { value in
|
||||
case let .timePicker(_, value, enabled):
|
||||
return ItemListInviteLinkTimeLimitItem(theme: presentationData.theme, strings: presentationData.strings, value: value, enabled: enabled, sectionId: self.section, updated: { value in
|
||||
arguments.updateState({ state in
|
||||
var updatedState = state
|
||||
if value != updatedState.time {
|
||||
@ -279,14 +345,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return updatedState
|
||||
})
|
||||
})
|
||||
case let .timeExpiryDate(theme, dateTimeFormat, value, active):
|
||||
case let .timeExpiryDate(theme, dateTimeFormat, value, active, enabled):
|
||||
let text: String
|
||||
if let value = value {
|
||||
text = stringForMediumDate(timestamp: value, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
} else {
|
||||
text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever
|
||||
}
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.InviteLink_Create_TimeLimitExpiryDate, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.InviteLink_Create_TimeLimitExpiryDate, enabled: enabled, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
arguments.dismissInput()
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
@ -298,7 +364,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
case let .timeCustomPicker(_, dateTimeFormat, date, displayingDateSelection, displayingTimeSelection):
|
||||
case let .timeCustomPicker(_, dateTimeFormat, date, displayingDateSelection, displayingTimeSelection, enabled):
|
||||
let _ = enabled
|
||||
let title = presentationData.strings.InviteLink_Create_TimeLimitExpiryTime
|
||||
return ItemListDatePickerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, date: date, title: title, displayingDateSelection: displayingDateSelection, displayingTimeSelection: displayingTimeSelection, sectionId: self.section, style: .blocks, toggleDateSelection: {
|
||||
arguments.updateState({ state in
|
||||
@ -329,8 +396,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .usageHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .usagePicker(_, dateTimeFormat, value):
|
||||
return ItemListInviteLinkUsageLimitItem(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, value: value, enabled: true, sectionId: self.section, updated: { value in
|
||||
case let .usagePicker(_, dateTimeFormat, value, enabled):
|
||||
return ItemListInviteLinkUsageLimitItem(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, value: value, enabled: enabled, sectionId: self.section, updated: { value in
|
||||
arguments.dismissInput()
|
||||
arguments.updateState({ state in
|
||||
var updatedState = state
|
||||
@ -342,14 +409,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return updatedState
|
||||
})
|
||||
})
|
||||
case let .usageCustomPicker(theme, value, focused, customValue):
|
||||
case let .usageCustomPicker(theme, value, focused, customValue, enabled):
|
||||
let text: String
|
||||
if let value = value, value != 0 {
|
||||
text = String(value)
|
||||
} else {
|
||||
text = focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited
|
||||
}
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, selectAllOnFocus: true, secondaryStyle: !customValue, tag: InviteLinksEditEntryTag.usage, sectionId: self.section, textUpdated: { updatedText in
|
||||
return ItemListSingleLineInputItem(context: arguments.context, presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, enabled: enabled, selectAllOnFocus: true, secondaryStyle: !customValue, tag: InviteLinksEditEntryTag.usage, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
if updatedText.isEmpty {
|
||||
@ -398,19 +465,40 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
|
||||
entries.append(.title(presentationData.theme, presentationData.strings.InviteLink_Create_LinkName, state.title))
|
||||
entries.append(.titleInfo(presentationData.theme, presentationData.strings.InviteLink_Create_LinkNameInfo))
|
||||
|
||||
if !isPublic {
|
||||
entries.append(.requestApproval(presentationData.theme, presentationData.strings.InviteLink_Create_RequestApproval, state.requestApproval))
|
||||
var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel
|
||||
if state.requestApproval {
|
||||
requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOnInfoChannel
|
||||
let isEditingEnabled = invite?.pricing == nil
|
||||
let isSubscription = state.subscriptionEnabled
|
||||
if !isGroup {
|
||||
//TODO:localize
|
||||
entries.append(.subscriptionFeeToggle(presentationData.theme, "Require Monthly Fee", state.subscriptionEnabled, isEditingEnabled))
|
||||
if state.subscriptionEnabled {
|
||||
entries.append(.subscriptionFee(presentationData.theme, "Stars amount per month", isEditingEnabled, state.subscriptionFee))
|
||||
}
|
||||
let infoText: String
|
||||
if let _ = invite, state.subscriptionEnabled {
|
||||
infoText = "If you need to change the subscription fee, create a new invite link with a different price."
|
||||
} else {
|
||||
requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel
|
||||
infoText = "Charge a subscription fee from people joining your channel via this link. [Learn More >]()"
|
||||
}
|
||||
entries.append(.subscriptionFeeInfo(presentationData.theme, infoText))
|
||||
}
|
||||
|
||||
if !isPublic {
|
||||
entries.append(.requestApproval(presentationData.theme, presentationData.strings.InviteLink_Create_RequestApproval, state.requestApproval, isEditingEnabled && !isSubscription))
|
||||
var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel
|
||||
if isSubscription {
|
||||
requestApprovalInfoText = "You can't enable admin approval for links that require a monthly fee."
|
||||
} else {
|
||||
if state.requestApproval {
|
||||
requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOnInfoChannel
|
||||
} else {
|
||||
requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel
|
||||
}
|
||||
}
|
||||
entries.append(.requestApprovalInfo(presentationData.theme, requestApprovalInfoText))
|
||||
}
|
||||
|
||||
entries.append(.timeHeader(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimit.uppercased()))
|
||||
entries.append(.timePicker(presentationData.theme, state.time))
|
||||
entries.append(.timePicker(presentationData.theme, state.time, isEditingEnabled))
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
var time: Int32?
|
||||
@ -419,21 +507,21 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
|
||||
} else if let value = state.time.value {
|
||||
time = currentTime + value
|
||||
}
|
||||
entries.append(.timeExpiryDate(presentationData.theme, presentationData.dateTimeFormat, time, state.pickingExpiryDate || state.pickingExpiryTime))
|
||||
entries.append(.timeExpiryDate(presentationData.theme, presentationData.dateTimeFormat, time, state.pickingExpiryDate || state.pickingExpiryTime, isEditingEnabled))
|
||||
if state.pickingExpiryDate || state.pickingExpiryTime {
|
||||
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, time, state.pickingExpiryDate, state.pickingExpiryTime))
|
||||
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, time, state.pickingExpiryDate, state.pickingExpiryTime, isEditingEnabled))
|
||||
}
|
||||
entries.append(.timeInfo(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimitInfo))
|
||||
|
||||
if !state.requestApproval || isPublic {
|
||||
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
|
||||
entries.append(.usagePicker(presentationData.theme, presentationData.dateTimeFormat, state.usage))
|
||||
entries.append(.usagePicker(presentationData.theme, presentationData.dateTimeFormat, state.usage, isEditingEnabled))
|
||||
|
||||
var customValue = false
|
||||
if case .custom = state.usage {
|
||||
customValue = true
|
||||
}
|
||||
entries.append(.usageCustomPicker(presentationData.theme, state.usage.value, state.pickingUsageLimit, customValue))
|
||||
entries.append(.usageCustomPicker(presentationData.theme, state.usage.value, state.pickingUsageLimit, customValue, isEditingEnabled))
|
||||
entries.append(.usageInfo(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimitInfo))
|
||||
}
|
||||
|
||||
@ -449,6 +537,8 @@ private struct InviteLinkEditControllerState: Equatable {
|
||||
var usage: InviteLinkUsageLimit
|
||||
var time: InviteLinkTimeLimit
|
||||
var requestApproval = false
|
||||
var subscriptionEnabled = false
|
||||
var subscriptionFee: Int64?
|
||||
var pickingExpiryDate = false
|
||||
var pickingExpiryTime = false
|
||||
var pickingUsageLimit = false
|
||||
@ -460,7 +550,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let initialState: InviteLinkEditControllerState
|
||||
if let invite = invite, case let .link(_, title, _, requestApproval, _, _, _, _, expireDate, usageLimit, count, _, _) = invite {
|
||||
if let invite = invite, case let .link(_, title, _, requestApproval, _, _, _, _, expireDate, usageLimit, count, _, pricing) = invite {
|
||||
var usageLimit = usageLimit
|
||||
if let limit = usageLimit, let count = count, count > 0 {
|
||||
usageLimit = limit - count
|
||||
@ -478,9 +568,9 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
timeLimit = .unlimited
|
||||
}
|
||||
|
||||
initialState = InviteLinkEditControllerState(title: title ?? "", usage: InviteLinkUsageLimit(value: usageLimit), time: timeLimit, requestApproval: requestApproval, pickingExpiryDate: false, pickingExpiryTime: false, pickingUsageLimit: false)
|
||||
initialState = InviteLinkEditControllerState(title: title ?? "", usage: InviteLinkUsageLimit(value: usageLimit), time: timeLimit, requestApproval: requestApproval, subscriptionEnabled: pricing != nil, subscriptionFee: pricing?.amount, pickingExpiryDate: false, pickingExpiryTime: false, pickingUsageLimit: false)
|
||||
} else {
|
||||
initialState = InviteLinkEditControllerState(title: "", usage: .unlimited, time: .unlimited, requestApproval: false, pickingExpiryDate: false, pickingExpiryTime: false, pickingUsageLimit: false)
|
||||
initialState = InviteLinkEditControllerState(title: "", usage: .unlimited, time: .unlimited, requestApproval: false, subscriptionEnabled: false, subscriptionFee: nil, pickingExpiryDate: false, pickingExpiryTime: false, pickingUsageLimit: false)
|
||||
}
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
@ -570,14 +660,21 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(invite == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Save), style: state.updating ? .activity : .bold, enabled: true, action: {
|
||||
var doneIsEnabled = true
|
||||
if state.subscriptionEnabled {
|
||||
if (state.subscriptionFee ?? 0) == 0 {
|
||||
doneIsEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(invite == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Save), style: state.updating ? .activity : .bold, enabled: doneIsEnabled, action: {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = true
|
||||
return updatedState
|
||||
}
|
||||
|
||||
let expireDate: Int32?
|
||||
var expireDate: Int32?
|
||||
if case let .custom(value) = state.time {
|
||||
expireDate = value
|
||||
} else if let value = state.time.value {
|
||||
@ -589,11 +686,20 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let titleString = state.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let title = titleString.isEmpty ? nil : titleString
|
||||
let usageLimit = state.usage.value
|
||||
let requestNeeded = state.requestApproval && !isPublic
|
||||
var usageLimit = state.usage.value
|
||||
var requestNeeded: Bool? = state.requestApproval && !isPublic
|
||||
|
||||
if invite == nil {
|
||||
let _ = (context.engine.peers.createPeerExportedInvitation(peerId: peerId, title: title, expireDate: expireDate, usageLimit: requestNeeded ? 0 : usageLimit, requestNeeded: requestNeeded, subscriptionPricing: nil)
|
||||
let subscriptionPricing: StarsSubscriptionPricing?
|
||||
if let subscriptionFee = state.subscriptionFee {
|
||||
subscriptionPricing = StarsSubscriptionPricing(
|
||||
period: context.account.testingEnvironment ? StarsSubscriptionPricing.testPeriod : StarsSubscriptionPricing.monthPeriod,
|
||||
amount: subscriptionFee
|
||||
)
|
||||
} else {
|
||||
subscriptionPricing = nil
|
||||
}
|
||||
let _ = (context.engine.peers.createPeerExportedInvitation(peerId: peerId, title: title, expireDate: expireDate, usageLimit: requestNeeded == true ? 0 : usageLimit, requestNeeded: requestNeeded, subscriptionPricing: subscriptionPricing)
|
||||
|> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
|
||||
|> deliverOnMainQueue).start(next: { invite in
|
||||
completion?(invite)
|
||||
@ -606,13 +712,24 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
})
|
||||
} else if let initialInvite = invite, case let .link(link, _, _, initialRequestApproval, _, _, _, _, initialExpireDate, initialUsageLimit, _, _, _) = initialInvite {
|
||||
if initialExpireDate == expireDate && initialUsageLimit == usageLimit && initialRequestApproval == requestNeeded {
|
||||
} else if let initialInvite = invite, case let .link(link, initialTitle, _, initialRequestApproval, _, _, _, _, initialExpireDate, initialUsageLimit, _, _, _) = initialInvite {
|
||||
if (initialExpireDate ?? 0) == expireDate && (initialUsageLimit ?? 0) == usageLimit && initialRequestApproval == requestNeeded && (initialTitle ?? "") == title {
|
||||
completion?(initialInvite)
|
||||
dismissImpl?()
|
||||
return
|
||||
}
|
||||
let _ = (context.engine.peers.editPeerExportedInvitation(peerId: peerId, link: link, title: title, expireDate: expireDate, usageLimit: requestNeeded ? 0 : usageLimit, requestNeeded: requestNeeded)
|
||||
|
||||
if (initialExpireDate ?? 0) == expireDate {
|
||||
expireDate = nil
|
||||
}
|
||||
if (initialUsageLimit ?? 0) == usageLimit {
|
||||
usageLimit = nil
|
||||
}
|
||||
if initialRequestApproval == requestNeeded {
|
||||
requestNeeded = nil
|
||||
}
|
||||
|
||||
let _ = (context.engine.peers.editPeerExportedInvitation(peerId: peerId, link: link, title: title, expireDate: expireDate, usageLimit: requestNeeded == true ? 0 : usageLimit, requestNeeded: requestNeeded)
|
||||
|> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
|
||||
|> deliverOnMainQueue).start(next: { invite in
|
||||
completion?(invite)
|
||||
@ -630,7 +747,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let previousState = previousState.swap(state)
|
||||
var animateChanges = false
|
||||
if let previousState = previousState, previousState.pickingExpiryDate != state.pickingExpiryDate || previousState.pickingExpiryTime != state.pickingExpiryTime || previousState.requestApproval != state.requestApproval {
|
||||
if let previousState = previousState, previousState.pickingExpiryDate != state.pickingExpiryDate || previousState.pickingExpiryTime != state.pickingExpiryTime || previousState.requestApproval != state.requestApproval || previousState.subscriptionEnabled != state.subscriptionEnabled {
|
||||
animateChanges = true
|
||||
}
|
||||
|
||||
|
@ -239,7 +239,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
arguments.createLink()
|
||||
})
|
||||
case let .link(_, _, invite, canEdit, _):
|
||||
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
return ItemListInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
arguments.openLink(invite)
|
||||
} contextAction: { invite, node, gesture in
|
||||
arguments.linkContextAction(invite, canEdit, node, gesture)
|
||||
@ -253,7 +253,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
arguments.deleteAllRevokedLinks()
|
||||
})
|
||||
case let .revokedLink(_, _, invite):
|
||||
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
return ItemListInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
arguments.openLink(invite)
|
||||
} contextAction: { invite, node, gesture in
|
||||
arguments.linkContextAction(invite, false, node, gesture)
|
||||
|
@ -20,6 +20,30 @@ import PresentationDataUtils
|
||||
import DirectionalPanGesture
|
||||
import UndoUI
|
||||
import QrCodeUI
|
||||
import TextFormat
|
||||
|
||||
private var subscriptionLinkIcon: UIImage? = {
|
||||
return generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let pathBounds = CGRect(origin: .zero, size: CGSize(width: 40.0, height: 40.0))
|
||||
context.addPath(CGPath(ellipseIn: pathBounds, transform: nil))
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [UIColor(rgb: 0x87d93b).cgColor, UIColor(rgb: 0x31b73b).cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/SubscriptionLink"), color: .white), let cgImage = image.cgImage {
|
||||
context.draw(cgImage, in: pathBounds)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
class InviteLinkViewInteraction {
|
||||
let context: AccountContext
|
||||
@ -50,6 +74,8 @@ private struct InviteLinkViewTransaction {
|
||||
|
||||
private enum InviteLinkViewEntryId: Hashable {
|
||||
case link
|
||||
case subscriptionHeader
|
||||
case subscriptionPricing
|
||||
case creatorHeader
|
||||
case creator
|
||||
case requestHeader
|
||||
@ -60,6 +86,8 @@ private enum InviteLinkViewEntryId: Hashable {
|
||||
|
||||
private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
case link(PresentationTheme, ExportedInvitation)
|
||||
case subscriptionHeader(PresentationTheme, String)
|
||||
case subscriptionPricing(PresentationTheme, String, String)
|
||||
case creatorHeader(PresentationTheme, String)
|
||||
case creator(PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32)
|
||||
case requestHeader(PresentationTheme, String, String, Bool)
|
||||
@ -71,6 +99,10 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case .link:
|
||||
return .link
|
||||
case .subscriptionHeader:
|
||||
return .subscriptionHeader
|
||||
case .subscriptionPricing:
|
||||
return .subscriptionPricing
|
||||
case .creatorHeader:
|
||||
return .creatorHeader
|
||||
case .creator:
|
||||
@ -94,6 +126,18 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionHeader(lhsTheme, lhsTitle):
|
||||
if case let .subscriptionHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionPricing(lhsTheme, lhsTitle, lhsSubtitle):
|
||||
if case let .subscriptionPricing(rhsTheme, rhsTitle, rhsSubtitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .creatorHeader(lhsTheme, lhsTitle):
|
||||
if case let .creatorHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
|
||||
return true
|
||||
@ -139,33 +183,47 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
switch rhs {
|
||||
case .link:
|
||||
return false
|
||||
case .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator, .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .subscriptionHeader:
|
||||
switch rhs {
|
||||
case .link, .subscriptionHeader:
|
||||
return false
|
||||
case .subscriptionPricing, .creatorHeader, .creator, .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .subscriptionPricing:
|
||||
switch rhs {
|
||||
case .link, .subscriptionHeader, .subscriptionPricing:
|
||||
return false
|
||||
case .creatorHeader, .creator, .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .creatorHeader:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader:
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader:
|
||||
return false
|
||||
case .creator, .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .creator:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator:
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator:
|
||||
return false
|
||||
case .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .requestHeader:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .requestHeader:
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator, .requestHeader:
|
||||
return false
|
||||
case .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case let .request(lhsIndex, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .requestHeader:
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator, .requestHeader:
|
||||
return false
|
||||
case let .request(rhsIndex, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -174,14 +232,14 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
}
|
||||
case .importerHeader:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .requestHeader, .request, .importerHeader:
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator, .requestHeader, .request, .importerHeader:
|
||||
return false
|
||||
case .importer:
|
||||
return true
|
||||
}
|
||||
case let .importer(lhsIndex, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
||||
return false
|
||||
case let .importer(rhsIndex, _, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -204,13 +262,22 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
interaction.contextAction(invite, node, gesture)
|
||||
}, viewAction: {
|
||||
})
|
||||
case let .subscriptionHeader(_, title):
|
||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
|
||||
case let .subscriptionPricing(_, title, subtitle):
|
||||
let attributedTitle = NSMutableAttributedString(string: title, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
||||
if let range = attributedTitle.string.range(of: "⭐️") {
|
||||
attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedTitle.string))
|
||||
attributedTitle.addAttribute(.baselineOffset, value: -1.0, range: NSRange(range, in: attributedTitle.string))
|
||||
}
|
||||
return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), icon: subscriptionLinkIcon, context: interaction.context, title: "", attributedTitle: attributedTitle, enabled: false, label: subtitle, labelStyle: .detailText, sectionId: 0, style: .plain, disclosureStyle: .none, noInsets: true, action: nil, clearHighlightAutomatically: true, tag: nil, shimmeringIndex: nil)
|
||||
case let .creatorHeader(_, title):
|
||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
|
||||
case let .creator(_, dateTimeFormat, peer, date):
|
||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, style: .plain, tag: nil)
|
||||
case let .importerHeader(_, title, subtitle, expired), let .requestHeader(_, title, subtitle, expired):
|
||||
let additionalText: SectionHeaderAdditionalText
|
||||
if !subtitle.isEmpty {
|
||||
@ -230,14 +297,14 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
}
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, style: .plain, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||
case let .request(_, _, dateTimeFormat, peer, date, loading):
|
||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, style: .plain, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -727,6 +794,13 @@ public final class InviteLinkViewController: ViewController {
|
||||
var entries: [InviteLinkViewEntry] = []
|
||||
|
||||
entries.append(.link(presentationData.theme, invite))
|
||||
|
||||
if let pricing = invite.pricing {
|
||||
//TODO:localize
|
||||
entries.append(.subscriptionHeader(presentationData.theme, "SUBSCRIPTION FEE"))
|
||||
entries.append(.subscriptionPricing(presentationData.theme, "⭐️\(pricing.amount) / month x \(state.count)", "You get approximately $\(Float(pricing.amount * Int64(state.count)) * 0.01) monthly"))
|
||||
}
|
||||
|
||||
entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased()))
|
||||
entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, EnginePeer(creatorPeer), date))
|
||||
|
||||
|
@ -7,6 +7,9 @@ import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import ShimmerEffect
|
||||
import TelegramCore
|
||||
import TextNodeWithEntities
|
||||
import AccountContext
|
||||
import TextFormat
|
||||
|
||||
func invitationAvailability(_ invite: ExportedInvitation) -> CGFloat {
|
||||
if case let .link(_, _, _, _, isRevoked, _, date, startDate, expireDate, usageLimit, count, _, _) = invite {
|
||||
@ -54,6 +57,7 @@ private enum ItemBackgroundColor: Equatable {
|
||||
}
|
||||
|
||||
public class ItemListInviteLinkItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let presentationData: ItemListPresentationData
|
||||
let invite: ExportedInvitation?
|
||||
let share: Bool
|
||||
@ -64,6 +68,7 @@ public class ItemListInviteLinkItem: ListViewItem, ItemListItem {
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
presentationData: ItemListPresentationData,
|
||||
invite: ExportedInvitation?,
|
||||
share: Bool,
|
||||
@ -73,6 +78,7 @@ public class ItemListInviteLinkItem: ListViewItem, ItemListItem {
|
||||
contextAction: ((ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void)?,
|
||||
tag: ItemListItemTag? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.invite = invite
|
||||
self.share = share
|
||||
@ -170,6 +176,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
private let titleNode: TextNode
|
||||
private let subtitleNode: TextNode
|
||||
private let pricingNode: TextNodeWithEntities
|
||||
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
@ -218,6 +225,8 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self.subtitleNode.isUserInteractionEnabled = false
|
||||
self.subtitleNode.contentMode = .left
|
||||
self.subtitleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.pricingNode = TextNodeWithEntities()
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
@ -237,6 +246,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self.offsetContainerNode.addSubnode(self.iconNode)
|
||||
self.offsetContainerNode.addSubnode(self.titleNode)
|
||||
self.offsetContainerNode.addSubnode(self.subtitleNode)
|
||||
self.offsetContainerNode.addSubnode(self.pricingNode.textNode)
|
||||
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self, let item = strongSelf.layoutParams?.0, let invite = item.invite, let contextAction = item.contextAction else {
|
||||
@ -266,6 +276,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self?.extractedBackgroundImageNode.image = nil
|
||||
}
|
||||
})
|
||||
transition.updateAlpha(node: strongSelf.pricingNode.textNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,6 +291,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
public func asyncLayout() -> (_ item: ItemListInviteLinkItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||
let makePricingLayout = TextNodeWithEntities.asyncLayout(self.pricingNode)
|
||||
|
||||
let currentItem = self.layoutParams?.0
|
||||
|
||||
@ -299,14 +311,19 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let color: ItemBackgroundColor
|
||||
let nextColor: ItemBackgroundColor
|
||||
let transitionFraction: CGFloat
|
||||
if let invite = item.invite, case let .link(_, _, _, _, isRevoked, _, _, _, expireDate, usageLimit, _, _, _) = invite {
|
||||
if let invite = item.invite, case let .link(_, _, _, _, isRevoked, _, _, _, expireDate, usageLimit, _, _, pricing) = invite {
|
||||
if isRevoked {
|
||||
color = .gray
|
||||
nextColor = .gray
|
||||
transitionFraction = 0.0
|
||||
} else if expireDate == nil && usageLimit == nil {
|
||||
color = .blue
|
||||
nextColor = .blue
|
||||
if let _ = pricing {
|
||||
color = .green
|
||||
nextColor = .green
|
||||
} else {
|
||||
color = .blue
|
||||
nextColor = .blue
|
||||
}
|
||||
transitionFraction = 0.0
|
||||
} else if availability >= 0.5 {
|
||||
color = .green
|
||||
@ -343,10 +360,10 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let inviteLink = item.invite?.link?.replacingOccurrences(of: "https://", with: "") ?? ""
|
||||
var titleText = inviteLink
|
||||
var subtitleText: String = ""
|
||||
var pricingAttributedText: NSMutableAttributedString?
|
||||
var timerValue: TimerNode.Value?
|
||||
|
||||
|
||||
if let invite = item.invite, case let .link(_, title, _, _, _, _, date, startDate, expireDate, usageLimit, count, requestedCount, _) = invite {
|
||||
if let invite = item.invite, case let .link(_, title, _, _, _, _, date, startDate, expireDate, usageLimit, count, requestedCount, subscriptionPricing) = invite {
|
||||
if let title = title, !title.isEmpty {
|
||||
titleText = title
|
||||
}
|
||||
@ -375,6 +392,19 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
subtitleText += item.presentationData.strings.MemberRequests_PeopleRequestedShort(requestedCount)
|
||||
}
|
||||
|
||||
if let subscriptionPricing {
|
||||
//TODO:localize
|
||||
let text = NSMutableAttributedString()
|
||||
text.append(NSAttributedString(string: "⭐️\(subscriptionPricing.amount)\n", font: Font.semibold(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor))
|
||||
text.append(NSAttributedString(string: "per month", font: Font.regular(13.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor))
|
||||
if let range = text.string.range(of: "⭐️") {
|
||||
text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: text.string))
|
||||
text.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(range, in: text.string))
|
||||
text.addAttribute(.baselineOffset, value: 2.5, range: NSRange(range, in: text.string))
|
||||
}
|
||||
pricingAttributedText = text
|
||||
}
|
||||
|
||||
if invite.isRevoked {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
@ -443,6 +473,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (pricingLayout, pricingApply) = makePricingLayout(TextNodeLayoutArguments(attributedString: pricingAttributedText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .right, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let titleSpacing: CGFloat = 1.0
|
||||
|
||||
@ -505,13 +536,18 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
|
||||
strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
if let _ = item.invite?.pricing {
|
||||
strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/SubscriptionLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else {
|
||||
strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InviteLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
let transition = ContainedViewLayoutTransition.immediate
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = subtitleApply()
|
||||
let _ = pricingApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, attemptSynchronous: false))
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
@ -607,6 +643,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.subtitleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.pricingNode.textNode, frame: CGRect(origin: CGPoint(x: layout.contentSize.width - rightInset - pricingLayout.size.width, y: floorToScreenPixels((layout.contentSize.height - pricingLayout.size.height) / 2.0)), size: pricingLayout.size))
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||
|
||||
|
@ -8,6 +8,7 @@ import ShimmerEffect
|
||||
import AvatarNode
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TextNodeWithEntities
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
||||
|
||||
@ -64,12 +65,13 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
public let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let disclosureStyle: ItemListDisclosureStyle
|
||||
let noInsets: Bool
|
||||
let action: (() -> Void)?
|
||||
let clearHighlightAutomatically: Bool
|
||||
public let tag: ItemListItemTag?
|
||||
public let shimmeringIndex: Int?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.context = context
|
||||
@ -88,6 +90,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
self.disclosureStyle = disclosureStyle
|
||||
self.noInsets = noInsets
|
||||
self.action = action
|
||||
self.clearHighlightAutomatically = clearHighlightAutomatically
|
||||
self.tag = tag
|
||||
@ -151,7 +154,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
var avatarNode: AvatarNode?
|
||||
let iconNode: ASImageNode
|
||||
let titleNode: TextNode
|
||||
let titleNode: TextNodeWithEntities
|
||||
let titleIconNode: ASImageNode
|
||||
public let labelNode: TextNode
|
||||
var additionalDetailLabelNode: TextNode?
|
||||
@ -196,8 +199,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode = TextNodeWithEntities()
|
||||
self.titleNode.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.titleIconNode = ASImageNode()
|
||||
self.titleIconNode.displayWithoutProcessing = true
|
||||
@ -224,7 +227,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.titleNode.textNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
|
||||
@ -252,7 +255,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode.textNode)
|
||||
let makeTitleWithEntitiesLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeAdditionalDetailLabelLayout = TextNode.asyncLayout(self.additionalDetailLabelNode)
|
||||
|
||||
@ -329,14 +333,14 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
var insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
let itemBackgroundColor: UIColor
|
||||
let itemSeparatorColor: UIColor
|
||||
|
||||
var leftInset = 16.0 + params.leftInset
|
||||
if item.icon != nil {
|
||||
leftInset += 43.0
|
||||
leftInset += item.noInsets ? 49.0 : 43.0
|
||||
} else if item.iconPeer != nil {
|
||||
leftInset += 46.0
|
||||
}
|
||||
@ -370,7 +374,11 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
maxTitleWidth -= 12.0
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: item.attributedTitle ?? NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: item.attributedTitle != nil ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let titleArguments = TextNodeLayoutArguments(attributedString: item.attributedTitle ?? NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: item.attributedTitle != nil ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())
|
||||
let (titleLayoutAndApply) = item.context == nil ? makeTitleLayout(titleArguments) : nil
|
||||
let (titleWithEntitiesLayoutAndApply) = item.context != nil ? makeTitleWithEntitiesLayout(titleArguments) : nil
|
||||
|
||||
let titleLayout: TextNodeLayout = (titleWithEntitiesLayoutAndApply?.0 ?? titleLayoutAndApply?.0)!
|
||||
|
||||
let detailFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||
|
||||
@ -455,6 +463,10 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
contentSize = CGSize(width: params.width, height: height)
|
||||
insets = itemListNeighborsPlainInsets(neighbors)
|
||||
if item.noInsets {
|
||||
insets.top = 0.0
|
||||
insets.bottom = 0.0
|
||||
}
|
||||
case .blocks:
|
||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
@ -531,8 +543,21 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
}
|
||||
|
||||
if let titleWithEntitiesApply = titleWithEntitiesLayoutAndApply?.1, let context = item.context {
|
||||
let _ = titleWithEntitiesApply(
|
||||
TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
placeholderColor: item.presentationData.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12),
|
||||
attemptSynchronous: false
|
||||
)
|
||||
)
|
||||
} else if let titleApply = titleLayoutAndApply?.1 {
|
||||
let _ = titleApply()
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = labelApply()
|
||||
|
||||
switch item.style {
|
||||
@ -607,7 +632,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - centralContentHeight) / 2.0)), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
strongSelf.titleNode.textNode.frame = titleFrame
|
||||
|
||||
if let updateBadgeImage = updatedLabelBadgeImage {
|
||||
if strongSelf.labelBadgeNode.supernode == nil {
|
||||
@ -746,7 +771,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let titleLineWidth: CGFloat = (shimmeringIndex % 2 == 0) ? 120.0 : 80.0
|
||||
let lineDiameter: CGFloat = 8.0
|
||||
|
||||
let titleFrame = strongSelf.titleNode.frame
|
||||
let titleFrame = strongSelf.titleNode.textNode.frame
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter))
|
||||
|
||||
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: contentSize)
|
||||
|
@ -4,6 +4,8 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TextNodeWithEntities
|
||||
import AccountContext
|
||||
|
||||
private let validIdentifierSet: CharacterSet = {
|
||||
var set = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!)
|
||||
@ -43,6 +45,7 @@ public enum ItemListSingleLineInputAlignment {
|
||||
}
|
||||
|
||||
public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext?
|
||||
let presentationData: ItemListPresentationData
|
||||
let title: NSAttributedString
|
||||
let text: String
|
||||
@ -65,7 +68,8 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let cleared: (() -> Void)?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
||||
public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.text = text
|
||||
@ -130,7 +134,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let titleNode: TextNode
|
||||
private let titleNode: TextNodeWithEntities
|
||||
private let measureTitleSizeNode: TextNode
|
||||
private let textNode: TextFieldNode
|
||||
private let clearIconNode: ASImageNode
|
||||
@ -154,7 +158,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode = TextNodeWithEntities()
|
||||
self.measureTitleSizeNode = TextNode()
|
||||
self.textNode = TextFieldNode()
|
||||
|
||||
@ -167,7 +171,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.titleNode.textNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.clearIconNode)
|
||||
self.addSubnode(self.clearButtonNode)
|
||||
@ -209,7 +213,8 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListSingleLineInputItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode.textNode)
|
||||
let makeTitleWithEntitiesLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
|
||||
let makeMeasureTitleSizeLayout = TextNode.asyncLayout(self.measureTitleSizeNode)
|
||||
|
||||
let currentItem = self.item
|
||||
@ -241,15 +246,22 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
}
|
||||
|
||||
let titleString = NSMutableAttributedString(attributedString: item.title)
|
||||
titleString.removeAttribute(NSAttributedString.Key.font, range: NSMakeRange(0, titleString.length))
|
||||
if !item.title.string.isSingleEmoji {
|
||||
titleString.removeAttribute(NSAttributedString.Key.font, range: NSMakeRange(0, titleString.length))
|
||||
}
|
||||
titleString.addAttributes([NSAttributedString.Key.font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)], range: NSMakeRange(0, titleString.length))
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let titleArguments = TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())
|
||||
|
||||
let (titleLayoutAndApply) = item.context == nil ? makeTitleLayout(titleArguments) : nil
|
||||
let (titleWithEntitiesLayoutAndApply) = item.context != nil ? makeTitleWithEntitiesLayout(titleArguments) : nil
|
||||
|
||||
let titleLayout: TextNodeLayout = (titleWithEntitiesLayoutAndApply?.0 ?? titleLayoutAndApply?.0)!
|
||||
|
||||
let (measureTitleLayout, measureTitleSizeApply) = makeMeasureTitleSizeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "A", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + 22.0)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
@ -280,8 +292,20 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
strongSelf.textNode.textField.textColor = item.secondaryStyle ? item.presentationData.theme.list.itemSecondaryTextColor : item.presentationData.theme.list.itemPrimaryTextColor
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||
if let titleWithEntitiesApply = titleWithEntitiesLayoutAndApply?.1, let context = item.context {
|
||||
let _ = titleWithEntitiesApply(
|
||||
TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
placeholderColor: item.presentationData.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12),
|
||||
attemptSynchronous: false
|
||||
)
|
||||
)
|
||||
} else if let titleApply = titleLayoutAndApply?.1 {
|
||||
let _ = titleApply()
|
||||
}
|
||||
strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||
|
||||
let _ = measureTitleSizeApply()
|
||||
|
||||
|
@ -11,10 +11,12 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
|
||||
private final class ResetPasswordControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateCodeText: (String) -> Void
|
||||
let openHelp: () -> Void
|
||||
|
||||
init(updateCodeText: @escaping (String) -> Void, openHelp: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateCodeText: @escaping (String) -> Void, openHelp: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateCodeText = updateCodeText
|
||||
self.openHelp = openHelp
|
||||
}
|
||||
@ -128,7 +130,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
|
||||
let saveDisposable = MetaDisposable()
|
||||
actionsDisposable.add(saveDisposable)
|
||||
|
||||
let arguments = ResetPasswordControllerArguments(updateCodeText: { updatedText in
|
||||
let arguments = ResetPasswordControllerArguments(context: context, updateCodeText: { updatedText in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.code = updatedText
|
||||
|
@ -15,10 +15,12 @@ import AuthorizationUtils
|
||||
import PhoneNumberFormat
|
||||
|
||||
private final class ChangePhoneNumberCodeControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateEntryText: (String) -> Void
|
||||
let next: () -> Void
|
||||
|
||||
init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateEntryText = updateEntryText
|
||||
self.next = next
|
||||
}
|
||||
@ -290,7 +292,7 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin
|
||||
}
|
||||
}
|
||||
|
||||
let arguments = ChangePhoneNumberCodeControllerArguments(updateEntryText: { updatedText in
|
||||
let arguments = ChangePhoneNumberCodeControllerArguments(context: context, updateEntryText: { updatedText in
|
||||
var initiateCheck = false
|
||||
updateState { state in
|
||||
if state.codeText.count < 5 && updatedText.count == 5 {
|
||||
|
@ -26,7 +26,7 @@ private func shareLink(for server: ProxyServerSettings) -> String {
|
||||
return link
|
||||
}
|
||||
|
||||
private final class proxyServerSettingsControllerArguments {
|
||||
private final class ProxyServerSettingsControllerArguments {
|
||||
let updateState: ((ProxyServerSettingsControllerState) -> ProxyServerSettingsControllerState) -> Void
|
||||
let share: () -> Void
|
||||
let usePasteboardSettings: () -> Void
|
||||
@ -113,7 +113,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! proxyServerSettingsControllerArguments
|
||||
let arguments = arguments as! ProxyServerSettingsControllerArguments
|
||||
switch self {
|
||||
case let .usePasteboardSettings(_, title):
|
||||
return ItemListActionItem(presentationData: presentationData, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
@ -158,7 +158,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry {
|
||||
case let .credentialsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .credentialsUsername(_, _, placeholder, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
|
||||
return ItemListSingleLineInputItem(context: nil, presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.username = value
|
||||
@ -306,7 +306,7 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
|
||||
|
||||
var shareImpl: (() -> Void)?
|
||||
|
||||
let arguments = proxyServerSettingsControllerArguments(updateState: { f in
|
||||
let arguments = ProxyServerSettingsControllerArguments(updateState: { f in
|
||||
updateState(f)
|
||||
}, share: {
|
||||
shareImpl?()
|
||||
|
@ -18,12 +18,14 @@ private enum CreatePasswordField {
|
||||
}
|
||||
|
||||
private final class CreatePasswordControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateFieldText: (CreatePasswordField, String) -> Void
|
||||
let selectNextInputItem: (CreatePasswordEntryTag) -> Void
|
||||
let save: () -> Void
|
||||
let cancelEmailConfirmation: () -> Void
|
||||
|
||||
init(updateFieldText: @escaping (CreatePasswordField, String) -> Void, selectNextInputItem: @escaping (CreatePasswordEntryTag) -> Void, save: @escaping () -> Void, cancelEmailConfirmation: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateFieldText: @escaping (CreatePasswordField, String) -> Void, selectNextInputItem: @escaping (CreatePasswordEntryTag) -> Void, save: @escaping () -> Void, cancelEmailConfirmation: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateFieldText = updateFieldText
|
||||
self.selectNextInputItem = selectNextInputItem
|
||||
self.save = save
|
||||
@ -321,7 +323,7 @@ func createPasswordController(context: AccountContext, createPasswordContext: Cr
|
||||
}
|
||||
}
|
||||
|
||||
let arguments = CreatePasswordControllerArguments(updateFieldText: { field, updatedText in
|
||||
let arguments = CreatePasswordControllerArguments(context: context, updateFieldText: { field, updatedText in
|
||||
updateState { state in
|
||||
var state = state
|
||||
switch field {
|
||||
|
@ -16,6 +16,7 @@ import PasswordSetupUI
|
||||
import Markdown
|
||||
|
||||
private final class TwoStepVerificationUnlockSettingsControllerArguments {
|
||||
let context: AccountContext
|
||||
let updatePasswordText: (String) -> Void
|
||||
let checkPassword: () -> Void
|
||||
let openForgotPassword: () -> Void
|
||||
@ -28,7 +29,8 @@ private final class TwoStepVerificationUnlockSettingsControllerArguments {
|
||||
let declinePasswordReset: () -> Void
|
||||
let resetPassword: () -> Void
|
||||
|
||||
init(updatePasswordText: @escaping (String) -> Void, checkPassword: @escaping () -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void, updateEmailCode: @escaping (String) -> Void, openConfirmEmail: @escaping () -> Void, declinePasswordReset: @escaping () -> Void, resetPassword: @escaping () -> Void) {
|
||||
init(context: AccountContext, updatePasswordText: @escaping (String) -> Void, checkPassword: @escaping () -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void, updateEmailCode: @escaping (String) -> Void, openConfirmEmail: @escaping () -> Void, declinePasswordReset: @escaping () -> Void, resetPassword: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updatePasswordText = updatePasswordText
|
||||
self.checkPassword = checkPassword
|
||||
self.openForgotPassword = openForgotPassword
|
||||
@ -423,7 +425,7 @@ public func twoStepVerificationUnlockSettingsController(context: AccountContext,
|
||||
})
|
||||
}
|
||||
|
||||
let arguments = TwoStepVerificationUnlockSettingsControllerArguments(updatePasswordText: { updatedText in
|
||||
let arguments = TwoStepVerificationUnlockSettingsControllerArguments(context: context, updatePasswordText: { updatedText in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.passwordText = updatedText
|
||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 185
|
||||
return 186
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
@ -341,7 +341,7 @@ private final class StarsContextImpl {
|
||||
return
|
||||
}
|
||||
var transactions = state.transactions
|
||||
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, media: []), at: 0)
|
||||
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, media: [], subscriptionPeriod: nil), at: 0)
|
||||
|
||||
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: state.balance + balance, subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
|
||||
}
|
||||
@ -408,7 +408,7 @@ private extension StarsContext.State.Transaction {
|
||||
|
||||
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
|
||||
let _ = subscriptionPeriod
|
||||
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, media: media)
|
||||
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, media: media, subscriptionPeriod: subscriptionPeriod)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -474,6 +474,7 @@ public final class StarsContext {
|
||||
public let transactionUrl: String?
|
||||
public let paidMessageId: MessageId?
|
||||
public let media: [Media]
|
||||
public let subscriptionPeriod: Int32?
|
||||
|
||||
public init(
|
||||
flags: Flags,
|
||||
@ -487,7 +488,8 @@ public final class StarsContext {
|
||||
transactionDate: Int32?,
|
||||
transactionUrl: String?,
|
||||
paidMessageId: MessageId?,
|
||||
media: [Media]
|
||||
media: [Media],
|
||||
subscriptionPeriod: Int32?
|
||||
) {
|
||||
self.flags = flags
|
||||
self.id = id
|
||||
@ -501,6 +503,7 @@ public final class StarsContext {
|
||||
self.transactionUrl = transactionUrl
|
||||
self.paidMessageId = paidMessageId
|
||||
self.media = media
|
||||
self.subscriptionPeriod = subscriptionPeriod
|
||||
}
|
||||
|
||||
public static func == (lhs: Transaction, rhs: Transaction) -> Bool {
|
||||
@ -540,6 +543,9 @@ public final class StarsContext {
|
||||
if !areMediaArraysEqual(lhs.media, rhs.media) {
|
||||
return false
|
||||
}
|
||||
if lhs.subscriptionPeriod != rhs.subscriptionPeriod {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1160,8 +1166,8 @@ public struct StarsSubscriptionPricing: Codable, Equatable {
|
||||
try container.encode(self.amount, forKey: .amount)
|
||||
}
|
||||
|
||||
public static let monthPeriod = 2592000
|
||||
public static let testPeriod = 300
|
||||
public static let monthPeriod: Int32 = 2592000
|
||||
public static let testPeriod: Int32 = 300
|
||||
}
|
||||
|
||||
extension StarsSubscriptionPricing {
|
||||
|
@ -203,7 +203,27 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
var delayedCloseOnOpenPeer = true
|
||||
switch subject {
|
||||
case let .transaction(transaction, parentPeer):
|
||||
if transaction.flags.contains(.isGift) {
|
||||
if let _ = transaction.subscriptionPeriod {
|
||||
//TODO:localize
|
||||
titleText = "Monthly Subscription Fee"
|
||||
descriptionText = ""
|
||||
count = transaction.count
|
||||
countOnTop = false
|
||||
transactionId = transaction.id
|
||||
via = nil
|
||||
messageId = nil
|
||||
date = transaction.date
|
||||
if case let .peer(peer) = transaction.peer {
|
||||
toPeer = peer
|
||||
} else {
|
||||
toPeer = nil
|
||||
}
|
||||
transactionPeer = transaction.peer
|
||||
media = []
|
||||
photo = nil
|
||||
isRefund = false
|
||||
isGift = false
|
||||
} else if transaction.flags.contains(.isGift) {
|
||||
titleText = strings.Stars_Gift_Received_Title
|
||||
descriptionText = strings.Stars_Gift_Received_Text
|
||||
count = transaction.count
|
||||
|
@ -219,6 +219,9 @@ final class StarsTransactionsListPanelComponent: Component {
|
||||
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||
if item.flags.contains(.isGift) {
|
||||
itemSubtitle = environment.strings.Stars_Intro_Transaction_Gift_Title
|
||||
} else if let _ = item.subscriptionPeriod {
|
||||
//TODO:localize
|
||||
itemSubtitle = "Monthly subscription fee"
|
||||
} else {
|
||||
itemSubtitle = nil
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ import ListSectionComponent
|
||||
import BundleIconComponent
|
||||
import TextFormat
|
||||
import UndoUI
|
||||
import ListActionItemComponent
|
||||
import StarsAvatarComponent
|
||||
|
||||
final class StarsTransactionsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -574,7 +576,44 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
contentHeight += balanceSize.height
|
||||
contentHeight += 44.0
|
||||
|
||||
let subscriptionsItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
let fontBaseDisplaySize = 17.0
|
||||
var subscriptionsItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
if let starsState = self.starsState {
|
||||
for subscription in starsState.subscriptions {
|
||||
var titleComponents: [AnyComponentWithIdentity<Empty>] = []
|
||||
titleComponents.append(
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: subscription.peer.compactDisplayTitle,
|
||||
font: Font.semibold(fontBaseDisplaySize),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)))
|
||||
)
|
||||
let itemLabel = NSAttributedString(string: "\(subscription.pricing.amount)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor)
|
||||
|
||||
subscriptionsItems.append(AnyComponentWithIdentity(
|
||||
id: subscription.id,
|
||||
component: AnyComponent(
|
||||
ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
|
||||
contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0),
|
||||
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: .peer(subscription.peer), photo: nil, media: [], backgroundColor: environment.theme.list.plainBackgroundColor))), false),
|
||||
icon: nil,
|
||||
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
|
||||
action: { [weak self] _ in
|
||||
guard let self, let _ = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if !subscriptionsItems.isEmpty {
|
||||
//TODO:localize
|
||||
|
@ -55,7 +55,7 @@ private final class SheetContent: CombinedComponent {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let closeButton = Child(Button.self)
|
||||
let title = Child(Text.self)
|
||||
let urlSection = Child(ListSectionComponent.self)
|
||||
let amountSection = Child(ListSectionComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
let balanceTitle = Child(MultilineTextComponent.self)
|
||||
let balanceValue = Child(MultilineTextComponent.self)
|
||||
@ -246,7 +246,7 @@ private final class SheetContent: CombinedComponent {
|
||||
amountFooter = nil
|
||||
}
|
||||
|
||||
let urlSection = urlSection.update(
|
||||
let amountSection = amountSection.update(
|
||||
component: ListSectionComponent(
|
||||
theme: theme,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
@ -283,12 +283,12 @@ private final class SheetContent: CombinedComponent {
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(urlSection
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + urlSection.size.height / 2.0))
|
||||
context.add(amountSection
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + amountSection.size.height / 2.0))
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
)
|
||||
contentSize.height += urlSection.size.height
|
||||
contentSize.height += amountSection.size.height
|
||||
contentSize.height += 32.0
|
||||
|
||||
let buttonString: String
|
||||
|
@ -250,12 +250,7 @@ func openResolvedUrlImpl(
|
||||
present(controller, nil)
|
||||
case let .instantView(webpage, anchor):
|
||||
let sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel)
|
||||
let pageController: ViewController
|
||||
if context.sharedContext.immediateExperimentalUISettings.browserExperiment {
|
||||
pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, anchor: anchor, sourceLocation: sourceLocation))
|
||||
} else {
|
||||
pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor)
|
||||
}
|
||||
let pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, anchor: anchor, sourceLocation: sourceLocation))
|
||||
navigationController?.pushViewController(pageController)
|
||||
case let .join(link):
|
||||
dismissInput()
|
||||
@ -288,6 +283,55 @@ func openResolvedUrlImpl(
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
case let .peek(peer, deadline):
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: ChatPeekTimeout(deadline: deadline, linkData: link)))
|
||||
case let .invite(invite):
|
||||
if let subscriptionPricing = invite.subscriptionPricing, let subscriptionFormId = invite.subscriptionFormId, let starsContext = context.starsContext {
|
||||
let inputData = Promise<BotCheckoutController.InputData?>()
|
||||
var photo: [TelegramMediaImageRepresentation] = []
|
||||
if let photoRepresentation = invite.photoRepresentation {
|
||||
photo.append(photoRepresentation)
|
||||
}
|
||||
let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
|
||||
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, flags: [], version: 0)
|
||||
|
||||
inputData.set(.single(BotCheckoutController.InputData(
|
||||
form: BotPaymentForm(
|
||||
id: subscriptionFormId,
|
||||
canSaveCredentials: false,
|
||||
passwordMissing: false,
|
||||
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil),
|
||||
paymentBotId: channel.id,
|
||||
providerId: nil,
|
||||
url: nil,
|
||||
nativeProvider: nil,
|
||||
savedInfo: nil,
|
||||
savedCredentials: [],
|
||||
additionalPaymentMethods: []
|
||||
),
|
||||
validatedFormInfo: nil,
|
||||
botPeer: EnginePeer(channel)
|
||||
)))
|
||||
|
||||
let starsInputData = combineLatest(
|
||||
inputData.get(),
|
||||
starsContext.state
|
||||
)
|
||||
|> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
|
||||
if let data, let state {
|
||||
return (state, data.form, data.botPeer)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { _ in
|
||||
let controller = context.sharedContext.makeStarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: .starsChatSubscription(hash: link), extendedMedia: [], inputData: starsInputData, completion: { _ in
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
})
|
||||
} else {
|
||||
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState), nil)
|
||||
}
|
||||
default:
|
||||
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
|
||||
|
Loading…
x
Reference in New Issue
Block a user