Various improvements

This commit is contained in:
Ilya Laktyushin 2023-12-11 14:26:22 +04:00
parent 6f0e415c80
commit 624f2d8e84
32 changed files with 1987 additions and 826 deletions

Binary file not shown.

View File

@ -10364,6 +10364,7 @@ Sorry for the inconvenience.";
"Chat.Giveaway.Info.Won" = "You won a prize in this giveaway. %@";
"Chat.Giveaway.Info.DidntWin" = "You didn't win a prize in this giveaway.";
"Chat.Giveaway.Info.ViewPrize" = "View My Prize";
"Chat.Giveaway.Info.AdditionalPrizes" = "**%1$@** also included **%2$@** in the prizes. Admins of the channel are responsible for delivering these prizes.";
"Chat.Giveaway.Info.FullDate" = "**%1$@** on **%2$@**";
@ -10390,6 +10391,7 @@ Sorry for the inconvenience.";
"Chat.Giveaway.Message.CountriesLastDelimiter" = " and ";
"Chat.Giveaway.Message.DateTitle" = "Winners Selection Date";
"Chat.Giveaway.Message.LearnMore" = "LEARN MORE";
"Chat.Giveaway.Message.With" = "with";
"GiftLink.Title" = "Gift Link";
"GiftLink.UsedTitle" = "Used Gift Link";
@ -10610,11 +10612,28 @@ Sorry for the inconvenience.";
"Chat.RemoveWallpaper.Text" = "Are you sure you want to reset the wallpaper?";
"Chat.RemoveWallpaper.Remove" = "Remove";
"Paint.CutOut" = "Cut Out";
"MediaEditor.Shortcut.Image" = "Image";
"MediaEditor.Shortcut.Location" = "Location";
"MediaEditor.Shortcut.Reaction" = "Reaction";
"MediaEditor.Shortcut.Audio" = "Audio";
"BoostGift.WinnersTitle" = "WINNERS";
"BoostGift.AdditionalPrizes" = "Additional Prizes";
"BoostGift.AdditionalPrizesPlaceholder" = "Enter Your Prize";
"BoostGift.AdditionalPrizesInfoOff" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions.";
"BoostGift.AdditionalPrizesInfoOn" = "All prizes: **%1$@** %2$@ %3$@.";
"BoostGift.AdditionalPrizesInfoSubscriptions_1" = "%@ Telegram Premium subscription";
"BoostGift.AdditionalPrizesInfoSubscriptions_any" = "%@ Telegram Premium subscriptions";
"BoostGift.AdditionalPrizesInfoWithSubscriptions_1" = "with %@ Telegram Premium subscription";
"BoostGift.AdditionalPrizesInfoWithSubscriptions_any" = "with %@ Telegram Premium subscriptions";
"BoostGift.AdditionalPrizesInfoForMonths_1" = "for **%@** month";
"BoostGift.AdditionalPrizesInfoForMonths_any" = "for **%@** months";
"BoostGift.Winners" = "Show Winners";
"BoostGift.WinnersInfo" = "Choose whether to make the list of winners public when the giveaway ends.";
"Story.ViewList.TitleReactions" = "Reactions";

View File

@ -657,7 +657,7 @@ private final class PendingInAppPurchaseState: Codable {
try container.encode(countries, forKey: .countries)
try container.encode(onlyNewSubscribers, forKey: .onlyNewSubscribers)
try container.encode(showWinners, forKey: .showWinners)
try container.encode(prizeDescription, forKey: .prizeDescription)
try container.encodeIfPresent(prizeDescription, forKey: .prizeDescription)
try container.encode(randomId, forKey: .randomId)
try container.encode(untilDate, forKey: .untilDate)
}

View File

@ -30,10 +30,11 @@ private final class CreateGiveawayControllerArguments {
let openCountriesSelection: () -> Void
let openPremiumIntro: () -> Void
let scrollToDate: () -> Void
let scrollToDescription: () -> Void
let setItemIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void
let removeChannel: (EnginePeer.Id) -> Void
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openCountriesSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void, scrollToDate: @escaping () -> Void, setItemIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeChannel: @escaping (EnginePeer.Id) -> Void) {
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openCountriesSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void, scrollToDate: @escaping () -> Void, scrollToDescription: @escaping () -> Void, setItemIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeChannel: @escaping (EnginePeer.Id) -> Void) {
self.context = context
self.updateState = updateState
self.dismissInput = dismissInput
@ -42,6 +43,7 @@ private final class CreateGiveawayControllerArguments {
self.openCountriesSelection = openCountriesSelection
self.openPremiumIntro = openPremiumIntro
self.scrollToDate = scrollToDate
self.scrollToDescription = scrollToDescription
self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions
self.removeChannel = removeChannel
}
@ -60,6 +62,7 @@ private enum CreateGiveawaySection: Int32 {
}
private enum CreateGiveawayEntryTag: ItemListItemTag {
case description
case date
func isEqual(to other: ItemListItemTag) -> Bool {
@ -94,12 +97,12 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case usersNew(PresentationTheme, String, String, Bool)
case usersInfo(PresentationTheme, String)
case winnersHeader(PresentationTheme, String)
case winners(PresentationTheme, String, Bool)
case winnersInfo(PresentationTheme, String)
case durationHeader(PresentationTheme, String)
case duration(Int32, PresentationTheme, Int32, String, String, String, String?, Bool)
case durationInfo(PresentationTheme, String)
case prizeDescriptionHeader(PresentationTheme, String)
case prizeDescription(PresentationTheme, String, String)
case prizeDescription(PresentationTheme, String, Bool)
case prizeDescriptionText(PresentationTheme, String, String, Int32)
case prizeDescriptionInfo(PresentationTheme, String)
case timeHeader(PresentationTheme, String)
@ -107,9 +110,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?, Int32?, Int32?, Bool, Bool)
case timeInfo(PresentationTheme, String)
case durationHeader(PresentationTheme, String)
case duration(Int32, PresentationTheme, Int32, String, String, String, String?, Bool)
case durationInfo(PresentationTheme, String)
case winners(PresentationTheme, String, Bool)
case winnersInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
@ -123,77 +125,75 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
return CreateGiveawaySection.channels.rawValue
case .usersHeader, .usersAll, .usersNew, .usersInfo:
return CreateGiveawaySection.users.rawValue
case .winnersHeader, .winners, .winnersInfo:
return CreateGiveawaySection.winners.rawValue
case .prizeDescriptionHeader, .prizeDescription, .prizeDescriptionInfo:
case .durationHeader, .duration, .durationInfo:
return CreateGiveawaySection.duration.rawValue
case .prizeDescription, .prizeDescriptionText, .prizeDescriptionInfo:
return CreateGiveawaySection.prizeDescription.rawValue
case .timeHeader, .timeExpiryDate, .timeCustomPicker, .timeInfo:
return CreateGiveawaySection.time.rawValue
case .durationHeader, .duration, .durationInfo:
return CreateGiveawaySection.duration.rawValue
case .winners, .winnersInfo:
return CreateGiveawaySection.winners.rawValue
}
}
var stableId: Int32 {
switch self {
case .header:
return -1
case .createGiveaway:
return 0
case .awardUsers:
return 1
case .prepaidHeader:
return 2
case .prepaid:
return 3
case .subscriptionsHeader:
return 4
case .subscriptions:
return 5
case .subscriptionsInfo:
return 6
case .channelsHeader:
return 7
case let .channel(index, _, _, _, _):
return 8 + index
case .channelAdd:
return 100
case .channelsInfo:
return 101
case .usersHeader:
return 102
case .usersAll:
return 103
case .usersNew:
return 104
case .usersInfo:
return 105
case .winnersHeader:
return 106
case .winners:
return 107
case .winnersInfo:
return 108
case .prizeDescriptionHeader:
return 109
case .prizeDescription:
return 110
case .prizeDescriptionInfo:
return 111
case .timeHeader:
return 112
case .timeExpiryDate:
return 113
case .timeCustomPicker:
return 114
case .timeInfo:
return 115
case .durationHeader:
return 116
case let .duration(index, _, _, _, _, _, _, _):
return 117 + index
case .durationInfo:
return 130
case .header:
return -1
case .createGiveaway:
return 0
case .awardUsers:
return 1
case .prepaidHeader:
return 2
case .prepaid:
return 3
case .subscriptionsHeader:
return 4
case .subscriptions:
return 5
case .subscriptionsInfo:
return 6
case .channelsHeader:
return 7
case let .channel(index, _, _, _, _):
return 8 + index
case .channelAdd:
return 100
case .channelsInfo:
return 101
case .usersHeader:
return 102
case .usersAll:
return 103
case .usersNew:
return 104
case .usersInfo:
return 105
case .durationHeader:
return 106
case let .duration(index, _, _, _, _, _, _, _):
return 107 + index
case .durationInfo:
return 200
case .prizeDescription:
return 201
case .prizeDescriptionText:
return 202
case .prizeDescriptionInfo:
return 203
case .timeHeader:
return 204
case .timeExpiryDate:
return 205
case .timeCustomPicker:
return 206
case .timeInfo:
return 207
case .winners:
return 208
case .winnersInfo:
return 209
}
}
@ -295,32 +295,32 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .winnersHeader(lhsTheme, lhsText):
if case let .winnersHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .durationHeader(lhsTheme, lhsText):
if case let .durationHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .winners(lhsTheme, lhsText, lhsValue):
if case let .winners(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .duration(lhsIndex, lhsTheme, lhsMonths, lhsTitle, lhsSubtitle, lhsLabel, lhsBadge, lhsIsSelected):
if case let .duration(rhsIndex, rhsTheme, rhsMonths, rhsTitle, rhsSubtitle, rhsLabel, rhsBadge, rhsIsSelected) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsMonths == rhsMonths, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel, lhsBadge == rhsBadge, lhsIsSelected == rhsIsSelected {
return true
} else {
return false
}
case let .winnersInfo(lhsTheme, lhsText):
if case let .winnersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .durationInfo(lhsTheme, lhsText):
if case let .durationInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .prizeDescriptionHeader(lhsTheme, lhsText):
if case let .prizeDescriptionHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .prizeDescription(lhsTheme, lhsText, lhsValue):
if case let .prizeDescription(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .prizeDescription(lhsTheme, lhsPlaceholder, lhsValue):
if case let .prizeDescription(rhsTheme, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue {
case let .prizeDescriptionText(lhsTheme, lhsPlaceholder, lhsValue, lhsCount):
if case let .prizeDescriptionText(rhsTheme, rhsPlaceholder, rhsValue, rhsCount) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsCount == rhsCount {
return true
} else {
return false
@ -355,20 +355,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .durationHeader(lhsTheme, lhsText):
if case let .durationHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .winners(lhsTheme, lhsText, lhsValue):
if case let .winners(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .duration(lhsIndex, lhsTheme, lhsMonths, lhsTitle, lhsSubtitle, lhsLabel, lhsBadge, lhsIsSelected):
if case let .duration(rhsIndex, rhsTheme, rhsMonths, rhsTitle, rhsSubtitle, rhsLabel, rhsBadge, rhsIsSelected) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsMonths == rhsMonths, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel, lhsBadge == rhsBadge, lhsIsSelected == rhsIsSelected {
return true
} else {
return false
}
case let .durationInfo(lhsTheme, lhsText):
if case let .durationInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .winnersInfo(lhsTheme, lhsText):
if case let .winnersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
@ -484,30 +478,44 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
})
case let .usersInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .winnersHeader(_, text):
case let .durationHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .winners(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .duration(_, _, months, title, subtitle, label, badge, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: subtitle, subtitleFont: .small, label: .generic(label), badge: badge, isSelected: isSelected, sectionId: self.section, action: {
arguments.updateState { state in
var updatedState = state
updatedState.showWinners = value
updatedState.selectedMonths = months
return updatedState
}
})
case let .winnersInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .prizeDescriptionHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .prizeDescription(_, placeholder, value):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
case let .durationInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
arguments.openPremiumIntro()
})
case let .prizeDescription(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.showPrizeDescription = value
return updatedState
}
})
case let .prizeDescriptionText(_, placeholder, value, count):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.prizeDescription = value
return updatedState
}
}, action: {})
}, updatedFocus: { focused in
if focused {
arguments.scrollToDescription()
}
}, action: {
arguments.dismissInput()
})
case let .prizeDescriptionInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .timeHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .timeExpiryDate(theme, dateTimeFormat, value, active):
@ -577,20 +585,16 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
}, tag: CreateGiveawayEntryTag.date)
case let .timeInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .durationHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .duration(_, _, months, title, subtitle, label, badge, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: subtitle, subtitleFont: .small, label: .generic(label), badge: badge, isSelected: isSelected, sectionId: self.section, action: {
case let .winners(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.selectedMonths = months
updatedState.showWinners = value
return updatedState
}
})
case let .durationInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
arguments.openPremiumIntro()
})
case let .winnersInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
}
}
@ -658,64 +662,7 @@ private func createGiveawayControllerEntries(
entries.append(.prepaid(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity), presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(prepaidGiveaway.months)").string, prepaidGiveaway))
}
if case .giveaway = state.mode {
if case .generic = subject {
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_QuantityTitle.uppercased(), presentationData.strings.BoostGift_QuantityBoosts(state.subscriptions * 4)))
entries.append(.subscriptions(presentationData.theme, state.subscriptions))
entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo))
}
entries.append(.channelsHeader(presentationData.theme, presentationData.strings.BoostGift_ChannelsTitle.uppercased()))
var index: Int32 = 0
let channels = [peerId] + state.channels
for channelId in channels {
if let channel = peers[channelId] {
entries.append(.channel(index, presentationData.theme, channel, channel.id == peerId ? state.subscriptions * 4 : nil, false))
}
index += 1
}
entries.append(.channelAdd(presentationData.theme, presentationData.strings.BoostGift_AddChannel))
entries.append(.channelsInfo(presentationData.theme, presentationData.strings.BoostGift_ChannelsInfo))
entries.append(.usersHeader(presentationData.theme, presentationData.strings.BoostGift_UsersTitle.uppercased()))
let countriesText: String
if state.countries.count > 2 {
countriesText = presentationData.strings.BoostGift_FromCountries(Int32(state.countries.count))
} else if !state.countries.isEmpty {
if state.countries.count == 2 {
let firstCountryCode = state.countries.first ?? ""
let secondCountryCode = state.countries.last ?? ""
let firstCountryName = locale.localizedString(forRegionCode: firstCountryCode) ?? firstCountryCode
let secondCountryName = locale.localizedString(forRegionCode: secondCountryCode) ?? secondCountryCode
countriesText = presentationData.strings.BoostGift_FromTwoCountries(firstCountryName, secondCountryName).string
} else {
let countryCode = state.countries.first ?? ""
let countryName = locale.localizedString(forRegionCode: countryCode) ?? countryCode
countriesText = presentationData.strings.BoostGift_FromOneCountry(countryName).string
}
} else {
countriesText = presentationData.strings.BoostGift_FromAllCountries
}
entries.append(.usersAll(presentationData.theme, presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible))
entries.append(.usersNew(presentationData.theme, presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible))
entries.append(.usersInfo(presentationData.theme, presentationData.strings.BoostGift_LimitSubscribersInfo))
entries.append(.winnersHeader(presentationData.theme, presentationData.strings.BoostGift_WinnersTitle.uppercased()))
entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners))
entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo))
entries.append(.prizeDescriptionHeader(presentationData.theme, "Additional Prizes".uppercased()))
entries.append(.prizeDescription(presentationData.theme, "Prize Description (Optional)", state.prizeDescription))
entries.append(.prizeDescriptionInfo(presentationData.theme, "Provide description of any additional prizes you plan to award to the winners, in addition to Telegram Premium."))
entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased()))
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime))
entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string))
}
if case .generic = subject {
let appendDurationEntries = {
entries.append(.durationHeader(presentationData.theme, presentationData.strings.BoostGift_DurationTitle.uppercased()))
let recipientCount: Int
@ -762,6 +709,82 @@ private func createGiveawayControllerEntries(
entries.append(.durationInfo(presentationData.theme, presentationData.strings.BoostGift_PremiumInfo))
}
switch state.mode {
case .giveaway:
if case .generic = subject {
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_QuantityTitle.uppercased(), presentationData.strings.BoostGift_QuantityBoosts(state.subscriptions * 4)))
entries.append(.subscriptions(presentationData.theme, state.subscriptions))
entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo))
}
entries.append(.channelsHeader(presentationData.theme, presentationData.strings.BoostGift_ChannelsTitle.uppercased()))
var index: Int32 = 0
let channels = [peerId] + state.channels
for channelId in channels {
if let channel = peers[channelId] {
entries.append(.channel(index, presentationData.theme, channel, channel.id == peerId ? state.subscriptions * 4 : nil, false))
}
index += 1
}
entries.append(.channelAdd(presentationData.theme, presentationData.strings.BoostGift_AddChannel))
entries.append(.channelsInfo(presentationData.theme, presentationData.strings.BoostGift_ChannelsInfo))
entries.append(.usersHeader(presentationData.theme, presentationData.strings.BoostGift_UsersTitle.uppercased()))
let countriesText: String
if state.countries.count > 2 {
countriesText = presentationData.strings.BoostGift_FromCountries(Int32(state.countries.count))
} else if !state.countries.isEmpty {
if state.countries.count == 2 {
let firstCountryCode = state.countries.first ?? ""
let secondCountryCode = state.countries.last ?? ""
let firstCountryName = locale.localizedString(forRegionCode: firstCountryCode) ?? firstCountryCode
let secondCountryName = locale.localizedString(forRegionCode: secondCountryCode) ?? secondCountryCode
countriesText = presentationData.strings.BoostGift_FromTwoCountries(firstCountryName, secondCountryName).string
} else {
let countryCode = state.countries.first ?? ""
let countryName = locale.localizedString(forRegionCode: countryCode) ?? countryCode
countriesText = presentationData.strings.BoostGift_FromOneCountry(countryName).string
}
} else {
countriesText = presentationData.strings.BoostGift_FromAllCountries
}
entries.append(.usersAll(presentationData.theme, presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible))
entries.append(.usersNew(presentationData.theme, presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible))
entries.append(.usersInfo(presentationData.theme, presentationData.strings.BoostGift_LimitSubscribersInfo))
if case .generic = subject {
appendDurationEntries()
}
entries.append(.prizeDescription(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizes, state.showPrizeDescription))
var prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOff
if state.showPrizeDescription {
entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions))
let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 3)
if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "")
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", subscriptionsString, monthsString).string
} else {
let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoWithSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "")
let description = "\(state.prizeDescription) \(subscriptionsString)"
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", description, monthsString).string
}
}
entries.append(.prizeDescriptionInfo(presentationData.theme, prizeDescriptionInfoText))
entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased()))
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime))
entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string))
entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners))
entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo))
case .gift:
appendDurationEntries()
}
return entries
}
@ -779,6 +802,7 @@ private struct CreateGiveawayControllerState: Equatable {
var countries: [String] = []
var onlyNewEligible: Bool = false
var showWinners: Bool = false
var showPrizeDescription: Bool = false
var prizeDescription: String = ""
var time: Int32
var pickingExpiryTime = false
@ -834,6 +858,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
var openPremiumIntroImpl: (() -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var scrollToDescriptionImpl: (() -> Void)?
var scrollToDateImpl: (() -> Void)?
var dismissImpl: (() -> Void)?
var dismissInputImpl: (() -> Void)?
@ -852,6 +877,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
openPremiumIntroImpl?()
}, scrollToDate: {
scrollToDateImpl?()
}, scrollToDescription: {
scrollToDescriptionImpl?()
}, setItemIdWithRevealedOptions: { itemId, fromItemId in
updateState { state in
var updatedState = state
@ -960,6 +987,9 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
if previousState.channels.count > state.channels.count {
animateChanges = true
}
if previousState.showPrizeDescription != state.showPrizeDescription {
animateChanges = true
}
}
var peers: [EnginePeer.Id: EnginePeer] = [:]
@ -981,7 +1011,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let controller = ItemListController(context: context, state: signal)
controller.navigationPresentation = .modal
controller.beganInteractiveDragging = {
dismissInputImpl?()
// dismissInputImpl?()
}
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root))
@ -1260,6 +1290,28 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
pushControllerImpl?(controller)
}
scrollToDescriptionImpl = { [weak controller] in
controller?.afterLayout({
guard let controller = controller else {
return
}
var resultItemNode: ListViewItemNode?
let _ = controller.frameForItemNode({ listItemNode in
if let itemNode = listItemNode as? ItemListItemNode {
if let tag = itemNode.tag as? CreateGiveawayEntryTag, tag == .description {
resultItemNode = listItemNode
return true
}
}
return false
})
if let resultItemNode = resultItemNode {
controller.ensureItemNodeVisible(resultItemNode)
}
})
}
scrollToDateImpl = { [weak controller] in
controller?.afterLayout({
guard let controller = controller else {

View File

@ -55,6 +55,11 @@ public func presentGiveawayInfoController(
dismissImpl?()
})]
var additionalPrizes = ""
if let prizeDescription = giveaway.prizeDescription, !prizeDescription.isEmpty {
additionalPrizes = "\n\n" + presentationData.strings.Chat_Giveaway_Info_AdditionalPrizes(peerName, "\(giveaway.quantity) \(prizeDescription)").string
}
switch giveawayInfo {
case let .ongoing(start, status):
let startDate = presentationData.strings.Chat_Giveaway_Info_FullDate(
@ -124,7 +129,7 @@ public func presentGiveawayInfoController(
participation = "\n\n\(participation)"
}
text = "\(intro)\n\n\(ending)\(participation)"
text = "\(intro)\(additionalPrizes)\n\n\(ending)\(participation)"
case let .finished(status, start, finish, _, activatedCount):
let startDate = presentationData.strings.Chat_Giveaway_Info_FullDate(
stringForMessageTimestamp(timestamp: start, dateTimeFormat: presentationData.dateTimeFormat),
@ -156,7 +161,7 @@ public func presentGiveawayInfoController(
if activatedCount > 0 {
ending += " " + presentationData.strings.Chat_Giveaway_Info_ActivatedLinks(activatedCount)
}
var result: String
switch status {
case .refunded:
@ -166,9 +171,9 @@ public func presentGiveawayInfoController(
dismissImpl?()
})]
case .notWon:
result = "\n\n" + presentationData.strings.Chat_Giveaway_Info_DidntWin
result = "**\(presentationData.strings.Chat_Giveaway_Info_DidntWin)**\n\n"
case let .won(slug):
result = "\n\n" + presentationData.strings.Chat_Giveaway_Info_Won("🏆").string
result = "**\(presentationData.strings.Chat_Giveaway_Info_Won("").string)**\n\n"
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Chat_Giveaway_Info_ViewPrize, action: {
dismissImpl?()
openLink(slug)
@ -177,7 +182,7 @@ public func presentGiveawayInfoController(
})]
}
text = "\(intro)\n\n\(ending)\(result)"
text = "\(result)\(intro)\(additionalPrizes)\n\n\(ending)"
}
let alertController = giveawayInfoAlertController(

View File

@ -464,7 +464,7 @@ class StatsOverviewItemNode: ListViewItemNode {
nil
)
let hasMessages = stats.viewsPerPost.current > 0
let hasMessages = stats.viewsPerPost.current > 0 || viewsPerPostDelta.hasValue
let hasStories = stats.viewsPerStory.current > 0 || viewsPerStoryDelta.hasValue
var items: [Int: (String, String, (String, ValueItemNode.DeltaColor)?)] = [:]

View File

@ -543,6 +543,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
dict[-1186937242] = { return Api.MessageMedia.parse_messageMediaGeoLive($0) }
dict[-626162256] = { return Api.MessageMedia.parse_messageMediaGiveaway($0) }
dict[-1323305567] = { return Api.MessageMedia.parse_messageMediaGiveawayResults($0) }
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
@ -825,6 +826,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1352440415] = { return Api.StoryItem.parse_storyItem($0) }
dict[1374088783] = { return Api.StoryItem.parse_storyItemDeleted($0) }
dict[-5388013] = { return Api.StoryItem.parse_storyItemSkipped($0) }
dict[-134495875] = { return Api.StoryPeerReaction.parse_storyPeerPublicRepost($0) }
dict[2112668723] = { return Api.StoryPeerReaction.parse_storyPeerReaction($0) }
dict[-1329730875] = { return Api.StoryView.parse_storyView($0) }
dict[-1923523370] = { return Api.StoryViews.parse_storyViews($0) }
dict[1964978502] = { return Api.TextWithEntities.parse_textWithEntities($0) }
@ -1176,7 +1179,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1222446760] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) }
dict[-1362048039] = { return Api.payments.ExportedInvoice.parse_exportedInvoice($0) }
dict[1130879648] = { return Api.payments.GiveawayInfo.parse_giveawayInfo($0) }
dict[-1966612121] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) }
dict[13456752] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) }
dict[-1610250415] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) }
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
@ -1216,6 +1219,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[291044926] = { return Api.stories.AllStories.parse_allStoriesNotModified($0) }
dict[-890861720] = { return Api.stories.PeerStories.parse_peerStories($0) }
dict[1574486984] = { return Api.stories.Stories.parse_stories($0) }
dict[-664005078] = { return Api.stories.StoryReactionsList.parse_storyReactionsList($0) }
dict[-560009955] = { return Api.stories.StoryViews.parse_storyViews($0) }
dict[1189722604] = { return Api.stories.StoryViewsList.parse_storyViewsList($0) }
dict[543450958] = { return Api.updates.ChannelDifference.parse_channelDifference($0) }
@ -1799,6 +1803,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.StoryItem:
_1.serialize(buffer, boxed)
case let _1 as Api.StoryPeerReaction:
_1.serialize(buffer, boxed)
case let _1 as Api.StoryView:
_1.serialize(buffer, boxed)
case let _1 as Api.StoryViews:
@ -2133,6 +2139,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.stories.Stories:
_1.serialize(buffer, boxed)
case let _1 as Api.stories.StoryReactionsList:
_1.serialize(buffer, boxed)
case let _1 as Api.stories.StoryViews:
_1.serialize(buffer, boxed)
case let _1 as Api.stories.StoryViewsList:

View File

@ -698,6 +698,7 @@ public extension Api {
case messageMediaGeo(geo: Api.GeoPoint)
case messageMediaGeoLive(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?)
case messageMediaGiveaway(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32, untilDate: Int32)
case messageMediaGiveawayResults(flags: Int32, channelId: Int64, launchMsgId: Int32, winnersCount: Int32, unclaimedCount: Int32, winners: [Int64], months: Int32, prizeDescription: String?)
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
@ -782,6 +783,23 @@ public extension Api {
serializeInt32(months, buffer: buffer, boxed: false)
serializeInt32(untilDate, buffer: buffer, boxed: false)
break
case .messageMediaGiveawayResults(let flags, let channelId, let launchMsgId, let winnersCount, let unclaimedCount, let winners, let months, let prizeDescription):
if boxed {
buffer.appendInt32(-1323305567)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(channelId, buffer: buffer, boxed: false)
serializeInt32(launchMsgId, buffer: buffer, boxed: false)
serializeInt32(winnersCount, buffer: buffer, boxed: false)
serializeInt32(unclaimedCount, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(winners.count))
for item in winners {
serializeInt64(item, buffer: buffer, boxed: false)
}
serializeInt32(months, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)}
break
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
if boxed {
buffer.appendInt32(-156940077)
@ -865,6 +883,8 @@ public extension Api {
return ("messageMediaGeoLive", [("flags", flags as Any), ("geo", geo as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)])
case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate):
return ("messageMediaGiveaway", [("flags", flags as Any), ("channels", channels as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)])
case .messageMediaGiveawayResults(let flags, let channelId, let launchMsgId, let winnersCount, let unclaimedCount, let winners, let months, let prizeDescription):
return ("messageMediaGiveawayResults", [("flags", flags as Any), ("channelId", channelId as Any), ("launchMsgId", launchMsgId as Any), ("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any), ("winners", winners as Any), ("months", months as Any), ("prizeDescription", prizeDescription as Any)])
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
return ("messageMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("receiptMsgId", receiptMsgId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)])
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
@ -1030,6 +1050,40 @@ public extension Api {
return nil
}
}
public static func parse_messageMediaGiveawayResults(_ reader: BufferReader) -> MessageMedia? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
var _6: [Int64]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
var _7: Int32?
_7 = reader.readInt32()
var _8: String?
if Int(_1!) & Int(1 << 1) != 0 {_8 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.MessageMedia.messageMediaGiveawayResults(flags: _1!, channelId: _2!, launchMsgId: _3!, winnersCount: _4!, unclaimedCount: _5!, winners: _6!, months: _7!, prizeDescription: _8)
}
else {
return nil
}
}
public static func parse_messageMediaInvoice(_ reader: BufferReader) -> MessageMedia? {
var _1: Int32?
_1 = reader.readInt32()

View File

@ -1190,6 +1190,82 @@ public extension Api {
}
}
public extension Api {
indirect enum StoryPeerReaction: TypeConstructorDescription {
case storyPeerPublicRepost(peerId: Api.Peer, story: Api.StoryItem)
case storyPeerReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .storyPeerPublicRepost(let peerId, let story):
if boxed {
buffer.appendInt32(-134495875)
}
peerId.serialize(buffer, true)
story.serialize(buffer, true)
break
case .storyPeerReaction(let peerId, let date, let reaction):
if boxed {
buffer.appendInt32(2112668723)
}
peerId.serialize(buffer, true)
serializeInt32(date, buffer: buffer, boxed: false)
reaction.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .storyPeerPublicRepost(let peerId, let story):
return ("storyPeerPublicRepost", [("peerId", peerId as Any), ("story", story as Any)])
case .storyPeerReaction(let peerId, let date, let reaction):
return ("storyPeerReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)])
}
}
public static func parse_storyPeerPublicRepost(_ reader: BufferReader) -> StoryPeerReaction? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Api.StoryItem?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StoryItem
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StoryPeerReaction.storyPeerPublicRepost(peerId: _1!, story: _2!)
}
else {
return nil
}
}
public static func parse_storyPeerReaction(_ reader: BufferReader) -> StoryPeerReaction? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.Reaction?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Reaction
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.StoryPeerReaction.storyPeerReaction(peerId: _1!, date: _2!, reaction: _3!)
}
else {
return nil
}
}
}
}
public extension Api {
enum StoryView: TypeConstructorDescription {
case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?)
@ -1430,111 +1506,3 @@ public extension Api {
}
}
public extension Api {
enum ThemeSettings: TypeConstructorDescription {
case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper):
if boxed {
buffer.appendInt32(-94849324)
}
serializeInt32(flags, buffer: buffer, boxed: false)
baseTheme.serialize(buffer, true)
serializeInt32(accentColor, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messageColors!.count))
for item in messageColors! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper):
return ("themeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any)])
}
}
public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.BaseTheme?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.BaseTheme
}
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
var _5: [Int32]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
var _6: Api.WallPaper?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.WallPaper
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6)
}
else {
return nil
}
}
}
}
public extension Api {
enum TopPeer: TypeConstructorDescription {
case topPeer(peer: Api.Peer, rating: Double)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .topPeer(let peer, let rating):
if boxed {
buffer.appendInt32(-305282981)
}
peer.serialize(buffer, true)
serializeDouble(rating, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .topPeer(let peer, let rating):
return ("topPeer", [("peer", peer as Any), ("rating", rating as Any)])
}
}
public static func parse_topPeer(_ reader: BufferReader) -> TopPeer? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Double?
_2 = reader.readDouble()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.TopPeer.topPeer(peer: _1!, rating: _2!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,111 @@
public extension Api {
enum ThemeSettings: TypeConstructorDescription {
case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper):
if boxed {
buffer.appendInt32(-94849324)
}
serializeInt32(flags, buffer: buffer, boxed: false)
baseTheme.serialize(buffer, true)
serializeInt32(accentColor, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messageColors!.count))
for item in messageColors! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper):
return ("themeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any)])
}
}
public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.BaseTheme?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.BaseTheme
}
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
var _5: [Int32]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
var _6: Api.WallPaper?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.WallPaper
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6)
}
else {
return nil
}
}
}
}
public extension Api {
enum TopPeer: TypeConstructorDescription {
case topPeer(peer: Api.Peer, rating: Double)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .topPeer(let peer, let rating):
if boxed {
buffer.appendInt32(-305282981)
}
peer.serialize(buffer, true)
serializeDouble(rating, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .topPeer(let peer, let rating):
return ("topPeer", [("peer", peer as Any), ("rating", rating as Any)])
}
}
public static func parse_topPeer(_ reader: BufferReader) -> TopPeer? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Double?
_2 = reader.readDouble()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.TopPeer.topPeer(peer: _1!, rating: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum TopPeerCategory: TypeConstructorDescription {
case topPeerCategoryBotsInline

View File

@ -911,7 +911,7 @@ public extension Api.payments {
public extension Api.payments {
enum GiveawayInfo: TypeConstructorDescription {
case giveawayInfo(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?)
case giveawayInfoResults(flags: Int32, startDate: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32, winners: [Api.User]?)
case giveawayInfoResults(flags: Int32, startDate: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -925,9 +925,9 @@ public extension Api.payments {
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(adminDisallowedChatId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {serializeString(disallowedCountry!, buffer: buffer, boxed: false)}
break
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount, let winners):
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount):
if boxed {
buffer.appendInt32(-1966612121)
buffer.appendInt32(13456752)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(startDate, buffer: buffer, boxed: false)
@ -935,11 +935,6 @@ public extension Api.payments {
serializeInt32(finishDate, buffer: buffer, boxed: false)
serializeInt32(winnersCount, buffer: buffer, boxed: false)
serializeInt32(activatedCount, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(winners!.count))
for item in winners! {
item.serialize(buffer, true)
}}
break
}
}
@ -948,8 +943,8 @@ public extension Api.payments {
switch self {
case .giveawayInfo(let flags, let startDate, let joinedTooEarlyDate, let adminDisallowedChatId, let disallowedCountry):
return ("giveawayInfo", [("flags", flags as Any), ("startDate", startDate as Any), ("joinedTooEarlyDate", joinedTooEarlyDate as Any), ("adminDisallowedChatId", adminDisallowedChatId as Any), ("disallowedCountry", disallowedCountry as Any)])
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount, let winners):
return ("giveawayInfoResults", [("flags", flags as Any), ("startDate", startDate as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any), ("winners", winners as Any)])
case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount):
return ("giveawayInfoResults", [("flags", flags as Any), ("startDate", startDate as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any)])
}
}
@ -989,19 +984,14 @@ public extension Api.payments {
_5 = reader.readInt32()
var _6: Int32?
_6 = reader.readInt32()
var _7: [Api.User]?
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, finishDate: _4!, winnersCount: _5!, activatedCount: _6!, winners: _7)
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, finishDate: _4!, winnersCount: _5!, activatedCount: _6!)
}
else {
return nil

View File

@ -1420,6 +1420,80 @@ public extension Api.stories {
}
}
public extension Api.stories {
enum StoryReactionsList: TypeConstructorDescription {
case storyReactionsList(flags: Int32, count: Int32, reactions: [Api.StoryPeerReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .storyReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset):
if boxed {
buffer.appendInt32(-664005078)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(reactions.count))
for item in reactions {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .storyReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset):
return ("storyReactionsList", [("flags", flags as Any), ("count", count as Any), ("reactions", reactions as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)])
}
}
public static func parse_storyReactionsList(_ reader: BufferReader) -> StoryReactionsList? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.StoryPeerReaction]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryPeerReaction.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.stories.StoryReactionsList.storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6)
}
else {
return nil
}
}
}
}
public extension Api.stories {
enum StoryViews: TypeConstructorDescription {
case storyViews(views: [Api.StoryViews], users: [Api.User])
@ -1540,175 +1614,3 @@ public extension Api.stories {
}
}
public extension Api.updates {
indirect enum ChannelDifference: TypeConstructorDescription {
case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User])
case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?)
case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
if boxed {
buffer.appendInt32(543450958)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .channelDifferenceEmpty(let flags, let pts, let timeout):
if boxed {
buffer.appendInt32(1041346555)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
if boxed {
buffer.appendInt32(-1531132162)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
dialog.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
return ("channelDifference", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any), ("newMessages", newMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any)])
case .channelDifferenceEmpty(let flags, let pts, let timeout):
return ("channelDifferenceEmpty", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any)])
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
return ("channelDifferenceTooLong", [("flags", flags as Any), ("timeout", timeout as Any), ("dialog", dialog as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Update]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _6: [Api.Chat]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!)
}
else {
return nil
}
}
public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3)
}
else {
return nil
}
}
public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() }
var _3: Api.Dialog?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Dialog
}
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,175 @@
public extension Api.updates {
indirect enum ChannelDifference: TypeConstructorDescription {
case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User])
case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?)
case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
if boxed {
buffer.appendInt32(543450958)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .channelDifferenceEmpty(let flags, let pts, let timeout):
if boxed {
buffer.appendInt32(1041346555)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
if boxed {
buffer.appendInt32(-1531132162)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
dialog.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
return ("channelDifference", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any), ("newMessages", newMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any)])
case .channelDifferenceEmpty(let flags, let pts, let timeout):
return ("channelDifferenceEmpty", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any)])
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
return ("channelDifferenceTooLong", [("flags", flags as Any), ("timeout", timeout as Any), ("dialog", dialog as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Update]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _6: [Api.Chat]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!)
}
else {
return nil
}
}
public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3)
}
else {
return nil
}
}
public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() }
var _3: Api.Dialog?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Dialog
}
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}
public extension Api.updates {
enum Difference: TypeConstructorDescription {
case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State)

View File

@ -3795,21 +3795,6 @@ public extension Api.functions.help {
})
}
}
public extension Api.functions.help {
static func getAppChangelog(prevAppVersion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1877938321)
serializeString(prevAppVersion, buffer: buffer, boxed: false)
return (FunctionDescription(name: "help.getAppChangelog", parameters: [("prevAppVersion", String(describing: prevAppVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.help {
static func getAppConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.help.AppConfig>) {
let buffer = Buffer()
@ -9084,6 +9069,26 @@ public extension Api.functions.stories {
})
}
}
public extension Api.functions.stories {
static func getStoryReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.StoryReactionsList>) {
let buffer = Buffer()
buffer.appendInt32(-1179482081)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {reaction!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stories.getStoryReactionsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("reaction", String(describing: reaction)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryReactionsList? in
let reader = BufferReader(buffer)
var result: Api.stories.StoryReactionsList?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.stories.StoryReactionsList
}
return result
})
}
}
public extension Api.functions.stories {
static func getStoryViewsList(flags: Int32, peer: Api.InputPeer, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.StoryViewsList>) {
let buffer = Buffer()

View File

@ -202,6 +202,7 @@ private var declaredEncodables: Void = {
declareEncodable(SynchronizePeerStoriesOperation.self, f: { SynchronizePeerStoriesOperation(decoder: $0) })
declareEncodable(MapVenue.self, f: { MapVenue(decoder: $0) })
declareEncodable(TelegramMediaGiveaway.self, f: { TelegramMediaGiveaway(decoder: $0) })
declareEncodable(TelegramMediaGiveawayResults.self, f: { TelegramMediaGiveawayResults(decoder: $0) })
declareEncodable(WebpagePreviewMessageAttribute.self, f: { WebpagePreviewMessageAttribute(decoder: $0) })
declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) })
return

View File

@ -427,10 +427,13 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
if (apiFlags & (1 << 0)) != 0 {
flags.insert(.onlyNewSubscribers)
}
if (apiFlags & (1 << 2)) != 0 {
flags.insert(.showWinners)
}
return (TelegramMediaGiveaway(flags: flags, channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, countries: countries ?? [], quantity: quantity, months: months, untilDate: untilDate, prizeDescription: prizeDescription), nil, nil, nil, nil)
case let .messageMediaGiveawayResults(apiFlags, channelId, launchMsgId, winnersCount, unclaimedCount, winners, months, prizeDescription):
var flags: TelegramMediaGiveawayResults.Flags = []
if (apiFlags & (1 << 0)) != 0 {
flags.insert(.refunded)
}
return (TelegramMediaGiveawayResults(flags: flags, launchMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: launchMsgId), winnersPeerIds: winners.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, winnersCount: winnersCount, unclaimedCount: unclaimedCount, months: months, prizeDescription: prizeDescription), nil, nil, nil, nil)
}
}

View File

@ -118,7 +118,6 @@ final class AccountTaskManager {
tasks.add(managedAutodownloadSettingsUpdates(accountManager: self.accountManager, network: self.stateManager.network).start())
tasks.add(managedTermsOfServiceUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedAppUpdateInfo(network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedAppChangelog(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, appVersion: self.networkArguments.appVersion).start())
tasks.add(managedPromoInfoUpdates(accountPeerId: self.accountPeerId, postbox: self.stateManager.postbox, network: self.stateManager.network, viewTracker: self.viewTracker).start())
tasks.add(managedLocalizationUpdatesOperations(accountManager: self.accountManager, postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedPendingPeerNotificationSettings(postbox: self.stateManager.postbox, network: self.stateManager.network).start())

View File

@ -5,39 +5,6 @@ import MtProtoKit
import TelegramApi
func managedAppChangelog(postbox: Postbox, network: Network, stateManager: AccountStateManager, appVersion: String) -> Signal<Void, NoError> {
return stateManager.isUpdating
|> filter { !$0 }
|> take(1)
|> mapToSignal { _ -> Signal<Void, NoError> in
return postbox.transaction { transaction -> AppChangelogState in
return transaction.getPreferencesEntry(key: PreferencesKeys.appChangelogState)?.get(AppChangelogState.self) ?? AppChangelogState.default
}
|> mapToSignal { appChangelogState -> Signal<Void, NoError> in
let appChangelogState = appChangelogState
if appChangelogState.checkedVersion == appVersion {
return .complete()
}
let previousVersion = appChangelogState.previousVersion
return network.request(Api.functions.help.getAppChangelog(prevAppVersion: previousVersion))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Void, NoError> in
if let updates = updates {
stateManager.addUpdates(updates)
}
return postbox.transaction { transaction in
updateAppChangelogState(transaction: transaction, { state in
var state = state
state.checkedVersion = appVersion
state.previousVersion = appVersion
return state
})
}
}
}
}
return .never()
}

View File

@ -0,0 +1,103 @@
import Postbox
public final class TelegramMediaGiveawayResults: Media, Equatable {
public struct Flags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let refunded = Flags(rawValue: 1 << 0)
}
public var id: MediaId? {
return nil
}
public var peerIds: [PeerId] {
return self.winnersPeerIds
}
public let flags: Flags
public let launchMessageId: MessageId
public let winnersPeerIds: [PeerId]
public let winnersCount: Int32
public let unclaimedCount: Int32
public let months: Int32
public let prizeDescription: String?
public init(flags: Flags, launchMessageId: MessageId, winnersPeerIds: [PeerId], winnersCount: Int32, unclaimedCount: Int32, months: Int32, prizeDescription: String?) {
self.flags = flags
self.launchMessageId = launchMessageId
self.winnersPeerIds = winnersPeerIds
self.winnersCount = winnersCount
self.unclaimedCount = unclaimedCount
self.months = months
self.prizeDescription = prizeDescription
}
public init(decoder: PostboxDecoder) {
self.flags = Flags(rawValue: decoder.decodeInt32ForKey("flg", orElse: 0))
self.launchMessageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("msgp", orElse: 0)), namespace: Namespaces.Message.Cloud, id: decoder.decodeInt32ForKey("msgi", orElse: 0))
self.winnersPeerIds = decoder.decodeInt64ArrayForKey("wnr").map { PeerId($0) }
self.winnersCount = decoder.decodeInt32ForKey("wnc", orElse: 0)
self.unclaimedCount = decoder.decodeInt32ForKey("unc", orElse: 0)
self.months = decoder.decodeInt32ForKey("mts", orElse: 0)
self.prizeDescription = decoder.decodeOptionalStringForKey("des")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.flags.rawValue, forKey: "flg")
encoder.encodeInt64(self.launchMessageId.peerId.toInt64(), forKey: "msgp")
encoder.encodeInt32(self.launchMessageId.id, forKey: "msgi")
encoder.encodeInt64Array(self.winnersPeerIds.map { $0.toInt64() }, forKey: "wnr")
encoder.encodeInt32(self.winnersCount, forKey: "wnc")
encoder.encodeInt32(self.unclaimedCount, forKey: "unc")
encoder.encodeInt32(self.months, forKey: "mts")
if let prizeDescription = self.prizeDescription {
encoder.encodeString(prizeDescription, forKey: "des")
} else {
encoder.encodeNil(forKey: "des")
}
}
public func isLikelyToBeUpdated() -> Bool {
return false
}
public func isEqual(to other: Media) -> Bool {
guard let other = other as? TelegramMediaGiveawayResults else {
return false
}
if self.flags != other.flags {
return false
}
if self.launchMessageId != other.launchMessageId {
return false
}
if self.winnersPeerIds != other.winnersPeerIds {
return false
}
if self.winnersCount != other.winnersCount {
return false
}
if self.unclaimedCount != other.unclaimedCount {
return false
}
if self.months != other.months {
return false
}
if self.prizeDescription != other.prizeDescription {
return false
}
return true
}
public func isSemanticallyEqual(to other: Media) -> Bool {
return self.isEqual(to: other)
}
public static func ==(lhs: TelegramMediaGiveawayResults, rhs: TelegramMediaGiveawayResults) -> Bool {
return lhs.isEqual(to: rhs)
}
}

View File

@ -19,47 +19,137 @@ public final class EngineStoryViewListContext {
case recentFirst
}
public final class Item: Equatable {
public let peer: EnginePeer
public let timestamp: Int32
public let storyStats: PeerStoryStats?
public let reaction: MessageReaction.Reaction?
public let reactionFile: TelegramMediaFile?
public init(
peer: EnginePeer,
timestamp: Int32,
storyStats: PeerStoryStats?,
reaction: MessageReaction.Reaction?,
reactionFile: TelegramMediaFile?
) {
self.peer = peer
self.timestamp = timestamp
self.storyStats = storyStats
self.reaction = reaction
self.reactionFile = reactionFile
public enum Item: Equatable {
public final class View: Equatable {
public let peer: EnginePeer
public let timestamp: Int32
public let storyStats: PeerStoryStats?
public let reaction: MessageReaction.Reaction?
public let reactionFile: TelegramMediaFile?
public init(
peer: EnginePeer,
timestamp: Int32,
storyStats: PeerStoryStats?,
reaction: MessageReaction.Reaction?,
reactionFile: TelegramMediaFile?
) {
self.peer = peer
self.timestamp = timestamp
self.storyStats = storyStats
self.reaction = reaction
self.reactionFile = reactionFile
}
public static func ==(lhs: View, rhs: View) -> Bool {
if lhs.peer != rhs.peer {
return false
}
if lhs.timestamp != rhs.timestamp {
return false
}
if lhs.storyStats != rhs.storyStats {
return false
}
if lhs.reaction != rhs.reaction {
return false
}
if lhs.reactionFile?.fileId != rhs.reactionFile?.fileId {
return false
}
return true
}
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs.peer != rhs.peer {
return false
public final class Repost: Equatable {
public let peer: EnginePeer
public let story: EngineStoryItem
public let storyStats: PeerStoryStats?
init(peer: EnginePeer, story: EngineStoryItem, storyStats: PeerStoryStats?) {
self.peer = peer
self.story = story
self.storyStats = storyStats
}
if lhs.timestamp != rhs.timestamp {
return false
public static func ==(lhs: Repost, rhs: Repost) -> Bool {
if lhs.peer != rhs.peer {
return false
}
if lhs.story != rhs.story {
return false
}
if lhs.storyStats != rhs.storyStats {
return false
}
return true
}
if lhs.storyStats != rhs.storyStats {
return false
}
case view(View)
case repost(Repost)
public var peer: EnginePeer {
switch self {
case let .view(view):
return view.peer
case let .repost(repost):
return repost.peer
}
if lhs.reaction != rhs.reaction {
return false
}
public var timestamp: Int32 {
switch self {
case let .view(view):
return view.timestamp
case let .repost(repost):
return repost.story.timestamp
}
if lhs.reactionFile?.fileId != rhs.reactionFile?.fileId {
return false
}
public var reaction: MessageReaction.Reaction? {
switch self {
case let .view(view):
return view.reaction
case .repost:
return nil
}
}
public var story: EngineStoryItem? {
switch self {
case .view:
return nil
case let .repost(repost):
return repost.story
}
}
public var storyStats: PeerStoryStats? {
switch self {
case let .view(view):
return view.storyStats
case let .repost(repost):
return repost.storyStats
}
}
public struct ItemHash: Hashable {
var peerId: EnginePeer.Id
var storyId: Int32?
}
public var uniqueId: ItemHash {
switch self {
case let .view(view):
return ItemHash(peerId: view.peer.id, storyId: nil)
case let .repost(repost):
return ItemHash(peerId: repost.peer.id, storyId: repost.story.id)
}
return true
}
}
public struct State: Equatable {
public var totalCount: Int
public var totalReactedCount: Int
@ -179,8 +269,8 @@ public final class EngineStoryViewListContext {
switch sortMode {
case .repostsFirst:
items.sort(by: { lhs, rhs in
if (lhs.reaction == nil) != (rhs.reaction == nil) {
return lhs.reaction != nil
if (lhs.story == nil) != (rhs.story == nil) {
return lhs.story != nil
}
if lhs.timestamp != rhs.timestamp {
return lhs.timestamp > rhs.timestamp
@ -189,6 +279,9 @@ public final class EngineStoryViewListContext {
})
case .reactionsFirst:
items.sort(by: { lhs, rhs in
if (lhs.story == nil) != (rhs.story == nil) {
return lhs.story == nil
}
if (lhs.reaction == nil) != (rhs.reaction == nil) {
return lhs.reaction != nil
}
@ -280,166 +373,270 @@ public final class EngineStoryViewListContext {
let searchQuery = self.searchQuery
let currentOffset = state.nextOffset
let limit = state.items.isEmpty ? 50 : 100
let signal: Signal<InternalState, NoError> = self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<InternalState, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
let signal: Signal<InternalState, NoError>
if peerId.namespace == Namespaces.Peer.CloudUser {
signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
var flags: Int32 = 0
switch listMode {
case .everyone:
break
case .contacts:
flags |= (1 << 0)
}
switch sortMode {
case .reactionsFirst:
flags |= (1 << 2)
case .recentFirst, .repostsFirst:
break
}
if searchQuery != nil {
flags |= (1 << 1)
}
return account.network.request(Api.functions.stories.getStoryViewsList(flags: flags, peer: inputPeer, q: searchQuery, id: storyId, offset: currentOffset?.value ?? "", limit: Int32(limit)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.StoryViewsList?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<InternalState, NoError> in
return account.postbox.transaction { transaction -> InternalState in
switch result {
case let .storyViewsList(_, count, reactionsCount, views, users, nextOffset):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
var items: [Item] = []
for view in views {
switch view {
case let .storyView(flags, userId, date, reaction):
let isBlocked = (flags & (1 << 0)) != 0
let isBlockedFromStories = (flags & (1 << 1)) != 0
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in
let previousData: CachedUserData
if let current = cachedData as? CachedUserData {
previousData = current
} else {
previousData = CachedUserData()
}
var updatedFlags = previousData.flags
if isBlockedFromStories {
updatedFlags.insert(.isBlockedFromStories)
} else {
updatedFlags.remove(.isBlockedFromStories)
}
return previousData.withUpdatedIsBlocked(isBlocked).withUpdatedFlags(updatedFlags)
})
if let peer = transaction.getPeer(peerId) {
let parsedReaction = reaction.flatMap(MessageReaction.Reaction.init(apiReaction:))
items.append(Item(
peer: EnginePeer(peer),
timestamp: date,
storyStats: transaction.getPeerStoryStats(peerId: peerId),
reaction: parsedReaction,
reactionFile: parsedReaction.flatMap { reaction -> TelegramMediaFile? in
switch reaction {
case .builtin:
return nil
case let .custom(fileId):
return transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile
}
|> mapToSignal { inputPeer -> Signal<InternalState, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
}
var flags: Int32 = 0
switch listMode {
case .everyone:
break
case .contacts:
flags |= (1 << 0)
}
switch sortMode {
case .reactionsFirst:
flags |= (1 << 2)
case .recentFirst, .repostsFirst:
break
}
if searchQuery != nil {
flags |= (1 << 1)
}
return account.network.request(Api.functions.stories.getStoryViewsList(flags: flags, peer: inputPeer, q: searchQuery, id: storyId, offset: currentOffset?.value ?? "", limit: Int32(limit)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.StoryViewsList?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<InternalState, NoError> in
return account.postbox.transaction { transaction -> InternalState in
switch result {
case let .storyViewsList(_, count, reactionsCount, views, users, nextOffset):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
var items: [Item] = []
for view in views {
switch view {
case let .storyView(flags, userId, date, reaction):
let isBlocked = (flags & (1 << 0)) != 0
let isBlockedFromStories = (flags & (1 << 1)) != 0
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in
let previousData: CachedUserData
if let current = cachedData as? CachedUserData {
previousData = current
} else {
previousData = CachedUserData()
}
))
}
}
}
if listMode == .everyone, searchQuery == nil {
if let storedItem = transaction.getStory(id: StoryId(peerId: account.peerId, id: storyId))?.get(Stories.StoredItem.self), case let .item(item) = storedItem, let currentViews = item.views {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: Stories.Item.Views(
seenCount: Int(count),
reactedCount: Int(reactionsCount),
forwardCount: currentViews.forwardCount,
seenPeerIds: currentViews.seenPeerIds,
reactions: currentViews.reactions,
hasList: currentViews.hasList
),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
var updatedFlags = previousData.flags
if isBlockedFromStories {
updatedFlags.insert(.isBlockedFromStories)
} else {
updatedFlags.remove(.isBlockedFromStories)
}
return previousData.withUpdatedIsBlocked(isBlocked).withUpdatedFlags(updatedFlags)
})
if let peer = transaction.getPeer(peerId) {
let parsedReaction = reaction.flatMap(MessageReaction.Reaction.init(apiReaction:))
items.append(.view(Item.View(
peer: EnginePeer(peer),
timestamp: date,
storyStats: transaction.getPeerStoryStats(peerId: peerId),
reaction: parsedReaction,
reactionFile: parsedReaction.flatMap { reaction -> TelegramMediaFile? in
switch reaction {
case .builtin:
return nil
case let .custom(fileId):
return transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile
}
}
)))
}
}
}
var currentItems = transaction.getStoryItems(peerId: account.peerId)
for i in 0 ..< currentItems.count {
if currentItems[i].id == storyId {
if case let .item(item) = currentItems[i].value.get(Stories.StoredItem.self), let currentViews = item.views {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: Stories.Item.Views(
seenCount: Int(count),
reactedCount: Int(reactionsCount),
forwardCount: currentViews.forwardCount,
seenPeerIds: currentViews.seenPeerIds,
reactions: currentViews.reactions,
hasList: currentViews.hasList
),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
if listMode == .everyone, searchQuery == nil {
if let storedItem = transaction.getStory(id: StoryId(peerId: account.peerId, id: storyId))?.get(Stories.StoredItem.self), case let .item(item) = storedItem, let currentViews = item.views {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: Stories.Item.Views(
seenCount: Int(count),
reactedCount: Int(reactionsCount),
forwardCount: currentViews.forwardCount,
seenPeerIds: currentViews.seenPeerIds,
reactions: currentViews.reactions,
hasList: currentViews.hasList
),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
}
}
var currentItems = transaction.getStoryItems(peerId: account.peerId)
for i in 0 ..< currentItems.count {
if currentItems[i].id == storyId {
if case let .item(item) = currentItems[i].value.get(Stories.StoredItem.self), let currentViews = item.views {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: Stories.Item.Views(
seenCount: Int(count),
reactedCount: Int(reactionsCount),
forwardCount: currentViews.forwardCount,
seenPeerIds: currentViews.seenPeerIds,
reactions: currentViews.reactions,
hasList: currentViews.hasList
),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp, isCloseFriends: updatedItem.isCloseFriends)
}
}
}
}
transaction.setStoryItems(peerId: account.peerId, items: currentItems)
}
return InternalState(totalCount: Int(count), totalReactedCount: Int(reactionsCount), items: items, canLoadMore: nextOffset != nil, nextOffset: nextOffset.flatMap { NextOffset(value: $0) })
case .none:
return InternalState(totalCount: 0, totalReactedCount: 0, items: [], canLoadMore: false, nextOffset: nil)
}
}
}
}
} else {
signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<InternalState, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
}
var flags: Int32 = 0
if case .repostsFirst = sortMode {
flags |= (1 << 2)
}
return account.network.request(Api.functions.stories.getStoryReactionsList(flags: flags, peer: inputPeer, id: storyId, reaction: nil, offset: currentOffset?.value, limit: Int32(limit)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.StoryReactionsList?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<InternalState, NoError> in
return account.postbox.transaction { transaction -> InternalState in
switch result {
case let .storyReactionsList(_, count, reactions, chats, users, nextOffset):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(chats: chats, users: users))
var items: [Item] = []
for reaction in reactions {
switch reaction {
case let .storyPeerReaction(peerId, date, reaction):
if let peer = transaction.getPeer(peerId.peerId) {
if let parsedReaction = MessageReaction.Reaction(apiReaction: reaction) {
let reactionFile: TelegramMediaFile?
switch parsedReaction {
case .builtin:
reactionFile = nil
case let .custom(fileId):
reactionFile = transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile
}
items.append(.view(Item.View(
peer: EnginePeer(peer),
timestamp: date,
storyStats: transaction.getPeerStoryStats(peerId: peer.id),
reaction: parsedReaction,
reactionFile: reactionFile
)))
}
}
case let .storyPeerPublicRepost(peerId, story):
if let peer = transaction.getPeer(peerId.peerId) {
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peer.id, transaction: transaction), case let .item(item) = storedItem, let media = item.media {
items.append(.repost(Item.Repost(
peer: EnginePeer(peer),
story: EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
reactedCount: views.reactedCount,
forwardCount: views.forwardCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return transaction.getPeer(id).flatMap(EnginePeer.init)
},
reactions: views.reactions,
hasList: views.hasList
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }
),
storyStats: transaction.getPeerStoryStats(peerId: peer.id)
)))
}
}
}
}
transaction.setStoryItems(peerId: account.peerId, items: currentItems)
return InternalState(totalCount: Int(count), totalReactedCount: Int(count), items: items, canLoadMore: nextOffset != nil, nextOffset: nextOffset.flatMap { NextOffset(value: $0) })
case .none:
return InternalState(totalCount: 0, totalReactedCount: 0, items: [], canLoadMore: false, nextOffset: nil)
}
return InternalState(totalCount: Int(count), totalReactedCount: Int(reactionsCount), items: items, canLoadMore: nextOffset != nil, nextOffset: nextOffset.flatMap { NextOffset(value: $0) })
case .none:
return InternalState(totalCount: 0, totalReactedCount: 0, items: [], canLoadMore: false, nextOffset: nil)
}
}
}
@ -457,21 +654,17 @@ public final class EngineStoryViewListContext {
var currentState = self.state ?? InternalState(
totalCount: 0, totalReactedCount: 0, items: [], canLoadMore: false, nextOffset: nil)
struct ItemHash: Hashable {
var peerId: EnginePeer.Id
}
if self.parentSource != nil {
currentState.items.removeAll()
}
var existingItems = Set<ItemHash>()
var existingItems = Set<Item.ItemHash>()
for item in currentState.items {
existingItems.insert(ItemHash(peerId: item.peer.id))
existingItems.insert(item.uniqueId)
}
for item in state.items {
let itemHash = ItemHash(peerId: item.peer.id)
let itemHash = item.uniqueId
if existingItems.contains(itemHash) {
continue
}
@ -481,7 +674,7 @@ public final class EngineStoryViewListContext {
var allReactedCount = 0
for item in currentState.items {
if item.reaction != nil {
if case let .view(view) = item, view.reaction != nil {
allReactedCount += 1
} else {
break
@ -516,15 +709,15 @@ public final class EngineStoryViewListContext {
for i in 0 ..< state.items.count {
let item = items[i]
let value = view.storyStats[item.peer.id]
if item.storyStats != value {
if case let .view(view) = item, view.storyStats != value {
updated = true
items[i] = Item(
peer: item.peer,
timestamp: item.timestamp,
items[i] = .view(Item.View(
peer: view.peer,
timestamp: view.timestamp,
storyStats: value,
reaction: item.reaction,
reactionFile: item.reactionFile
)
reaction: view.reaction,
reactionFile: view.reactionFile
))
}
}
if updated {

View File

@ -18,6 +18,7 @@ public enum EngineMedia: Equatable {
case webpage(TelegramMediaWebpage)
case story(TelegramMediaStory)
case giveaway(TelegramMediaGiveaway)
case giveawayResults(TelegramMediaGiveawayResults)
}
public extension EngineMedia {
@ -53,6 +54,8 @@ public extension EngineMedia {
return story.id
case let .giveaway(giveaway):
return giveaway.id
case let .giveawayResults(giveawayResults):
return giveawayResults.id
}
}
}
@ -90,6 +93,8 @@ public extension EngineMedia {
self = .story(story)
case let giveaway as TelegramMediaGiveaway:
self = .giveaway(giveaway)
case let giveawayResults as TelegramMediaGiveawayResults:
self = .giveawayResults(giveawayResults)
default:
preconditionFailure()
}
@ -127,6 +132,8 @@ public extension EngineMedia {
return story
case let .giveaway(giveaway):
return giveaway
case let .giveawayResults(giveawayResults):
return giveawayResults
}
}
}

View File

@ -120,7 +120,7 @@ func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, m
} else {
return .ongoing(startDate: startDate, status: .notQualified)
}
case let .giveawayInfoResults(flags, startDate, giftCodeSlug, finishDate, winnersCount, activatedCount, _):
case let .giveawayInfoResults(flags, startDate, giftCodeSlug, finishDate, winnersCount, activatedCount):
let status: PremiumGiveawayInfo.ResultStatus
if (flags & (1 << 1)) != 0 {
status = .refunded

View File

@ -221,6 +221,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} else if let _ = media as? TelegramMediaGiveaway {
result.append((message, ChatMessageGiveawayBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false
} else if let _ = media as? TelegramMediaGiveawayResults {
result.append((message, ChatMessageGiveawayBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false
} else if let _ = media as? TelegramMediaUnsupported {
isUnsupportedMedia = true
needReactions = false

View File

@ -33,9 +33,12 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
private let prizeTitleNode: TextNode
private let prizeTextNode: TextNode
private let additionalPrizeTitleNode: TextNode
private let additionalPrizeSeparatorNode: TextNode
private let additionalPrizeTextNode: TextNode
private let additionalPrizeLeftLine: ASDisplayNode
private let additionalPrizeRightLine: ASDisplayNode
private let participantsTitleNode: TextNode
private let participantsTextNode: TextNode
@ -84,9 +87,12 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
self.prizeTitleNode = TextNode()
self.prizeTextNode = TextNode()
self.additionalPrizeTitleNode = TextNode()
self.additionalPrizeSeparatorNode = TextNode()
self.additionalPrizeTextNode = TextNode()
self.additionalPrizeLeftLine = ASDisplayNode()
self.additionalPrizeRightLine = ASDisplayNode()
self.participantsTitleNode = TextNode()
self.participantsTextNode = TextNode()
@ -107,8 +113,10 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
self.addSubnode(self.prizeTitleNode)
self.addSubnode(self.prizeTextNode)
self.addSubnode(self.additionalPrizeTitleNode)
self.addSubnode(self.additionalPrizeSeparatorNode)
self.addSubnode(self.additionalPrizeTextNode)
self.addSubnode(self.additionalPrizeLeftLine)
self.addSubnode(self.additionalPrizeRightLine)
self.addSubnode(self.participantsTitleNode)
self.addSubnode(self.participantsTextNode)
self.addSubnode(self.countriesTextNode)
@ -189,7 +197,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let makePrizeTitleLayout = TextNode.asyncLayout(self.prizeTitleNode)
let makePrizeTextLayout = TextNode.asyncLayout(self.prizeTextNode)
let makeAdditionalPrizeTitleLayout = TextNode.asyncLayout(self.additionalPrizeTitleNode)
let makeAdditionalPrizeSeparatorLayout = TextNode.asyncLayout(self.additionalPrizeSeparatorNode)
let makeAdditionalPrizeTextLayout = TextNode.asyncLayout(self.additionalPrizeTextNode)
let makeParticipantsTitleLayout = TextNode.asyncLayout(self.participantsTitleNode)
@ -210,9 +218,12 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
return { item, layoutConstants, _, _, constrainedSize, _ in
var giveaway: TelegramMediaGiveaway?
var giveawayResults: TelegramMediaGiveawayResults?
for media in item.message.media {
if let media = media as? TelegramMediaGiveaway {
giveaway = media;
} else if let media = media as? TelegramMediaGiveawayResults {
giveawayResults = media
}
}
@ -228,6 +239,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let backgroundColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first! : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill.first!
let textColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
let secondaryTextColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
let lineColor = secondaryTextColor.withMultipliedAlpha(0.2)
let accentColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
var badgeTextColor: UIColor = .white
if badgeTextColor.distance(to: accentColor) < 1 {
@ -239,15 +252,38 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
}
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor)
let prizeTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle, font: titleFont, textColor: textColor)
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 2)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor)
let prizeTitleString = NSAttributedString(string: giveawayResults != nil ? "Winners Selected!" : item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle, font: titleFont, textColor: textColor)
var prizeTextString: NSAttributedString?
var additionalPrizeTitleString: NSAttributedString?
var additionalPrizeSeparatorString: NSAttributedString?
var additionalPrizeTextString: NSAttributedString?
if let giveaway {
var prizeDescription: String?
if let description = giveaway.prizeDescription {
prizeDescription = description
}
var trimSubscriptionCount = false
if let prizeDescription {
additionalPrizeSeparatorString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_With, font: textFont, textColor: secondaryTextColor)
additionalPrizeTextString = parseMarkdownIntoAttributedString("**\(giveaway.quantity)** \(prizeDescription)", attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: textColor),
linkAttribute: { url in
return ("URL", url)
}
), textAlignment: .center)
trimSubscriptionCount = true
}
var subscriptionsString = item.presentationData.strings.Chat_Giveaway_Message_Subscriptions(giveaway.quantity)
if trimSubscriptionCount {
subscriptionsString = subscriptionsString.replacingOccurrences(of: "**\(giveaway.quantity)** ", with: "")
}
prizeTextString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_Giveaway_Message_PrizeText(
item.presentationData.strings.Chat_Giveaway_Message_Subscriptions(giveaway.quantity),
subscriptionsString,
item.presentationData.strings.Chat_Giveaway_Message_Months(giveaway.months)
).string, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
@ -257,14 +293,18 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
return ("URL", url)
}
), textAlignment: .center)
if let prizeDescription = giveaway.prizeDescription {
additionalPrizeTitleString = NSAttributedString(string: "Additional Prize", font: titleFont, textColor: textColor)
additionalPrizeTextString = NSAttributedString(string: prizeDescription, font: textFont, textColor: textColor)
}
} else if let giveawayResults {
prizeTextString = parseMarkdownIntoAttributedString("**\(giveawayResults.winnersCount)** winners of the [Giveaway]() were randomly selected by Telegram.", attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
linkAttribute: { url in
return ("URL", url)
}
), textAlignment: .center)
}
let participantsTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_ParticipantsTitle, font: titleFont, textColor: textColor)
let participantsTitleString = NSAttributedString(string: giveawayResults != nil ? "Winners" : item.presentationData.strings.Chat_Giveaway_Message_ParticipantsTitle, font: titleFont, textColor: textColor)
let participantsText: String
let countriesText: String
@ -314,15 +354,26 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
}
let participantsTextString = NSAttributedString(string: participantsText, font: textFont, textColor: textColor)
let countriesTextString = NSAttributedString(string: countriesText, font: textFont, textColor: textColor)
let dateTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_DateTitle, font: titleFont, textColor: textColor)
var dateTitleText = item.presentationData.strings.Chat_Giveaway_Message_DateTitle
if let giveawayResults {
if giveawayResults.winnersCount > giveawayResults.winnersPeerIds.count {
let moreCount = giveawayResults.winnersCount - Int32(giveawayResults.winnersPeerIds.count)
dateTitleText = "and \(moreCount) more!"
} else {
dateTitleText = ""
}
}
let dateTitleString = NSAttributedString(string: dateTitleText, font: titleFont, textColor: textColor)
var dateTextString: NSAttributedString?
if let giveaway {
dateTextString = NSAttributedString(string: stringForFullDate(timestamp: giveaway.untilDate, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), font: textFont, textColor: textColor)
} else if let _ = giveawayResults {
dateTextString = NSAttributedString(string: "All winners received gift links in private messages.", font: textFont, textColor: textColor)
}
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, hidesHeaders: true)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let sideInsets = layoutConstants.text.bubbleInsets.right * 2.0
@ -331,13 +382,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let (badgeTextLayout, badgeTextApply) = makeBadgeTextLayout(TextNodeLayoutArguments(attributedString: badgeString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (prizeTitleLayout, prizeTitleApply) = makePrizeTitleLayout(TextNodeLayoutArguments(attributedString: prizeTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (prizeTextLayout, prizeTextApply) = makePrizeTextLayout(TextNodeLayoutArguments(attributedString: prizeTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (additionalPrizeTitleLayout, additionalPrizeTitleApply) = makeAdditionalPrizeTitleLayout(TextNodeLayoutArguments(attributedString: additionalPrizeTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (additionalPrizeTextLayout, additionalPrizeTextApply) = makeAdditionalPrizeTextLayout(TextNodeLayoutArguments(attributedString: additionalPrizeTextString, backgroundColor: nil, maximumNumberOfLines: 6, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (additionalPrizeSeparatorLayout, additionalPrizeSeparatorApply) = makeAdditionalPrizeSeparatorLayout(TextNodeLayoutArguments(attributedString: additionalPrizeSeparatorString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (prizeTextLayout, prizeTextApply) = makePrizeTextLayout(TextNodeLayoutArguments(attributedString: prizeTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (participantsTitleLayout, participantsTitleApply) = makeParticipantsTitleLayout(TextNodeLayoutArguments(attributedString: participantsTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (participantsTextLayout, participantsTextApply) = makeParticipantsTextLayout(TextNodeLayoutArguments(attributedString: participantsTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
@ -426,17 +477,22 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true)
let months = giveaway?.months ?? 0
let animationName: String
switch months {
case 12:
animationName = "Gift12"
case 6:
animationName = "Gift6"
case 3:
animationName = "Gift3"
default:
animationName = "Gift3"
let months = giveaway?.months ?? 0
if let _ = giveaway {
switch months {
case 12:
animationName = "Gift12"
case 6:
animationName = "Gift6"
case 3:
animationName = "Gift3"
default:
animationName = "Gift3"
}
} else {
animationName = "Celebrate"
}
var maxContentWidth: CGFloat = 0.0
@ -445,7 +501,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
}
maxContentWidth = max(maxContentWidth, prizeTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, prizeTextLayout.size.width)
maxContentWidth = max(maxContentWidth, additionalPrizeTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, additionalPrizeSeparatorLayout.size.width)
maxContentWidth = max(maxContentWidth, additionalPrizeTextLayout.size.width)
maxContentWidth = max(maxContentWidth, participantsTitleLayout.size.width)
maxContentWidth = max(maxContentWidth, participantsTextLayout.size.width)
@ -460,6 +516,10 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
channelPeers.append(EnginePeer(peer))
}
}
} else {
if let peer = item.message.peers[item.message.id.peerId] {
channelPeers.append(EnginePeer(peer))
}
}
let (channelsWidth, continueChannelLayout) = makeChannelsLayout(item.context, 220.0, channelPeers, accentColor, accentColor.withAlphaComponent(0.1), incoming, item.presentationData.theme.theme.overallDarkAppearance)
maxContentWidth = max(maxContentWidth, channelsWidth)
@ -475,14 +535,17 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
var layoutSize = CGSize(width: boundingWidth, height: 49.0 + prizeTitleLayout.size.height + prizeTextLayout.size.height + participantsTitleLayout.size.height + participantsTextLayout.size.height + dateTitleLayout.size.height + dateTextLayout.size.height + buttonSize.height + buttonSpacing + 120.0)
var layoutSize = CGSize(width: boundingWidth, height: 49.0 + prizeTitleLayout.size.height + prizeTextLayout.size.height + participantsTitleLayout.size.height + participantsTextLayout.size.height + dateTextLayout.size.height + buttonSize.height + buttonSpacing + 120.0)
if additionalPrizeTextLayout.size.height > 0.0 {
layoutSize.height += additionalPrizeTitleLayout.size.height + additionalPrizeTextLayout.size.height + 7.0
layoutSize.height += additionalPrizeSeparatorLayout.size.height + additionalPrizeTextLayout.size.height + 7.0
}
if countriesTextLayout.size.height > 0.0 {
layoutSize.height += countriesTextLayout.size.height + 7.0
}
if dateTitleLayout.size.height > 0.0 {
layoutSize.height += dateTitleLayout.size.height
}
layoutSize.height += channelButtonsSize.height
if let statusSizeAndApply = statusSizeAndApply {
@ -504,7 +567,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let _ = badgeTextApply()
let _ = prizeTitleApply()
let _ = prizeTextApply()
let _ = additionalPrizeTitleApply()
let _ = additionalPrizeSeparatorApply()
let _ = additionalPrizeTextApply()
let _ = participantsTitleApply()
let _ = participantsTextApply()
@ -519,8 +582,9 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
var originY: CGFloat = 0.0
let iconSize = CGSize(width: 140.0, height: 140.0)
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - iconSize.width) / 2.0), y: originY - 40.0), size: iconSize)
let animationOffset: CGFloat = giveaway != nil ? -40.0 : 15.0
let iconSize = giveaway != nil ? CGSize(width: 140.0, height: 140.0) : CGSize(width: 84.0, height: 84.0)
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - iconSize.width) / 2.0), y: originY + animationOffset), size: iconSize)
strongSelf.animationNode.updateLayout(size: iconSize)
let badgeTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - badgeTextLayout.size.width) / 2.0) + 1.0, y: originY + 88.0), size: badgeTextLayout.size)
@ -534,16 +598,35 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
strongSelf.prizeTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - prizeTitleLayout.size.width) / 2.0), y: originY), size: prizeTitleLayout.size)
originY += prizeTitleLayout.size.height + smallSpacing
if additionalPrizeTextLayout.size.height > 0.0 {
strongSelf.additionalPrizeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - additionalPrizeTextLayout.size.width) / 2.0), y: originY), size: additionalPrizeTextLayout.size)
originY += additionalPrizeTextLayout.size.height + smallSpacing
let separatorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - additionalPrizeSeparatorLayout.size.width) / 2.0), y: originY), size: additionalPrizeSeparatorLayout.size)
strongSelf.additionalPrizeSeparatorNode.frame = separatorFrame
originY += additionalPrizeSeparatorLayout.size.height + smallSpacing
let lineSpacing: CGFloat = 7.0
let lineWidth = (layoutSize.width - additionalPrizeSeparatorLayout.size.width - (27.0 + lineSpacing) * 2.0) / 2.0
let lineHeight: CGFloat = 1.0 - UIScreenPixel
let lineSize = CGSize(width: lineWidth, height: lineHeight)
strongSelf.additionalPrizeLeftLine.backgroundColor = lineColor
strongSelf.additionalPrizeLeftLine.isHidden = false
strongSelf.additionalPrizeLeftLine.frame = CGRect(origin: CGPoint(x: separatorFrame.minX - lineSize.width - lineSpacing, y: floorToScreenPixels(separatorFrame.midY - lineSize.height / 2.0)), size: lineSize)
strongSelf.additionalPrizeRightLine.backgroundColor = lineColor
strongSelf.additionalPrizeRightLine.isHidden = false
strongSelf.additionalPrizeRightLine.frame = CGRect(origin: CGPoint(x: separatorFrame.maxX + lineSpacing, y: floorToScreenPixels(separatorFrame.midY - lineSize.height / 2.0)), size: lineSize)
} else {
strongSelf.additionalPrizeLeftLine.isHidden = true
strongSelf.additionalPrizeRightLine.isHidden = true
}
strongSelf.prizeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - prizeTextLayout.size.width) / 2.0), y: originY), size: prizeTextLayout.size)
originY += prizeTextLayout.size.height + largeSpacing
if additionalPrizeTextLayout.size.height > 0.0 {
strongSelf.additionalPrizeTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - additionalPrizeTitleLayout.size.width) / 2.0), y: originY), size: additionalPrizeTitleLayout.size)
originY += additionalPrizeTitleLayout.size.height + smallSpacing
strongSelf.additionalPrizeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - additionalPrizeTextLayout.size.width) / 2.0), y: originY), size: additionalPrizeTextLayout.size)
originY += additionalPrizeTextLayout.size.height + largeSpacing
}
strongSelf.participantsTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTitleLayout.size.width) / 2.0), y: originY), size: participantsTitleLayout.size)
originY += participantsTitleLayout.size.height + smallSpacing
strongSelf.participantsTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTextLayout.size.width) / 2.0), y: originY), size: participantsTextLayout.size)

View File

@ -4034,13 +4034,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let expiringStoryList = PeerExpiringStoryListContext(account: context.account, peerId: peerId)
self.expiringStoryList = expiringStoryList
self.storyUploadProgressDisposable = (
combineLatest(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> distinctUntilChanged,
context.engine.messages.allStoriesUploadProgress()
|> map { value -> Float? in
return value[peerId]
}
|> distinctUntilChanged
combineLatest(
queue: Queue.mainQueue(),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> distinctUntilChanged,
context.engine.messages.allStoriesUploadProgress()
|> map { value -> Float? in
return value[peerId]
}
|> distinctUntilChanged
)).startStrict(next: { [weak self] peer, value in
guard let self else {
return

View File

@ -28,6 +28,7 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/ContextUI",
"//submodules/TextFormat",
"//submodules/PhotoResources",
],
visibility = [
"//visibility:public",

View File

@ -18,9 +18,11 @@ import EmojiStatusComponent
import ContextUI
import EmojiTextAttachmentView
import TextFormat
import PhotoResources
private let avatarFont = avatarPlaceholderFont(size: 15.0)
private let readIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/MenuReadIcon"), color: .white)?.withRenderingMode(.alwaysTemplate)
private let repostIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Stories/HeaderRepost"), color: .white)?.withRenderingMode(.alwaysTemplate)
private let checkImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)?.withRenderingMode(.alwaysTemplate)
private let disclosureImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
@ -51,6 +53,7 @@ public final class PeerListItemComponent: Component {
public enum SubtitleAccessory: Equatable {
case none
case checks
case repost
}
public enum RightAccessory: Equatable {
@ -105,6 +108,7 @@ public final class PeerListItemComponent: Component {
let presence: EnginePeer.Presence?
let rightAccessory: RightAccessory
let reaction: Reaction?
let story: EngineStoryItem?
let selectionState: SelectionState
let selectionPosition: SelectionPosition
let isEnabled: Bool
@ -112,6 +116,7 @@ public final class PeerListItemComponent: Component {
let action: (EnginePeer) -> Void
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
let openStories: ((EnginePeer, AvatarNode) -> Void)?
let openStory: ((EnginePeer, Int32, UIView) -> Void)?
public init(
context: AccountContext,
@ -127,13 +132,15 @@ public final class PeerListItemComponent: Component {
presence: EnginePeer.Presence?,
rightAccessory: RightAccessory = .none,
reaction: Reaction? = nil,
story: EngineStoryItem? = nil,
selectionState: SelectionState,
selectionPosition: SelectionPosition = .left,
isEnabled: Bool = true,
hasNext: Bool,
action: @escaping (EnginePeer) -> Void,
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
openStories: ((EnginePeer, AvatarNode) -> Void)? = nil
openStories: ((EnginePeer, AvatarNode) -> Void)? = nil,
openStory: ((EnginePeer, Int32, UIView) -> Void)? = nil
) {
self.context = context
self.theme = theme
@ -148,6 +155,7 @@ public final class PeerListItemComponent: Component {
self.presence = presence
self.rightAccessory = rightAccessory
self.reaction = reaction
self.story = story
self.selectionState = selectionState
self.selectionPosition = selectionPosition
self.isEnabled = isEnabled
@ -155,6 +163,7 @@ public final class PeerListItemComponent: Component {
self.action = action
self.contextAction = contextAction
self.openStories = openStories
self.openStory = openStory
}
public static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool {
@ -197,6 +206,9 @@ public final class PeerListItemComponent: Component {
if lhs.reaction != rhs.reaction {
return false
}
if lhs.story != rhs.story {
return false
}
if lhs.selectionState != rhs.selectionState {
return false
}
@ -232,6 +244,9 @@ public final class PeerListItemComponent: Component {
private var file: TelegramMediaFile?
private var fileDisposable: Disposable?
private var imageButtonView: HighlightTrackingButton?
private var imageNode: TransformImageNode?
private var component: PeerListItemComponent?
private weak var state: EmptyComponentState?
@ -340,6 +355,13 @@ public final class PeerListItemComponent: Component {
component.openStories?(peer, self.avatarNode)
}
@objc private func imageButtonPressed() {
guard let component = self.component, let peer = component.peer, let story = component.story, let imageNode = self.imageNode else {
return
}
component.openStory?(peer, story.id, imageNode.view)
}
private func updateReactionLayer() {
guard let component = self.component else {
return
@ -696,24 +718,34 @@ public final class PeerListItemComponent: Component {
if let labelView = self.label.view {
var iconLabelOffset: CGFloat = 0.0
if case .checks = component.subtitleAccessory {
if case .none = component.subtitleAccessory {
if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
}
} else {
let iconView: UIImageView
if let current = self.iconView {
iconView = current
} else {
iconView = UIImageView(image: readIconImage)
iconView.tintColor = component.theme.list.itemSecondaryTextColor
var image: UIImage?
var color: UIColor = component.theme.list.itemSecondaryTextColor
if case .checks = component.subtitleAccessory {
image = readIconImage
} else if case .repost = component.subtitleAccessory {
image = repostIconImage
color = UIColor(rgb: 0x34c759)
}
iconView = UIImageView(image: image)
iconView.tintColor = color
self.iconView = iconView
self.containerButton.addSubview(iconView)
}
if let image = iconView.image {
iconLabelOffset = image.size.width + 4.0
transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing + 3.0 + floor((labelSize.height - image.size.height) * 0.5)), size: image.size))
transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing + 2.0 + floor((labelSize.height - image.size.height) * 0.5)), size: image.size))
}
} else if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
}
if labelView.superview == nil {
@ -825,6 +857,72 @@ public final class PeerListItemComponent: Component {
transition.setFrame(layer: reactionLayer, frame: adjustedIconFrame)
}
var imageMedia: Media?
if let story = component.story {
if let image = story.media._asMedia() as? TelegramMediaImage {
imageMedia = image
} else if let file = story.media._asMedia() as? TelegramMediaFile {
imageMedia = file
}
}
if let peer = component.peer, let story = component.story, let imageMedia {
let contentImageSize = CGSize(width: 30.0, height: 42.0)
var dimensions: CGSize?
if let imageMedia = imageMedia as? TelegramMediaImage {
dimensions = largestRepresentationForPhoto(imageMedia)?.dimensions.cgSize
} else if let imageMedia = imageMedia as? TelegramMediaFile {
dimensions = imageMedia.dimensions?.cgSize
}
let imageButtonView: HighlightTrackingButton
let imageNode: TransformImageNode
if let current = self.imageNode, let currentButton = self.imageButtonView {
imageNode = current
imageButtonView = currentButton
} else {
imageNode = TransformImageNode()
imageNode.displaysAsynchronously = false
imageNode.isUserInteractionEnabled = false
self.imageNode = imageNode
imageButtonView = HighlightTrackingButton()
imageButtonView.addTarget(self, action: #selector(self.imageButtonPressed), for: .touchUpInside)
self.imageButtonView = imageButtonView
self.containerButton.addSubview(imageNode.view)
self.addSubview(imageButtonView)
var imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if let peerReference = PeerReference(peer._asPeer()) {
if let image = imageMedia as? TelegramMediaImage {
imageSignal = mediaGridMessagePhoto(account: component.context.account, userLocation: .peer(peer.id), photoReference: .story(peer: peerReference, id: story.id, media: image))
} else if let file = imageMedia as? TelegramMediaFile {
imageSignal = mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .peer(peer.id), videoReference: .story(peer: peerReference, id: story.id, media: file), autoFetchFullSizeThumbnail: true)
}
}
if let imageSignal {
imageNode.setSignal(imageSignal)
}
}
if let dimensions {
let makeImageLayout = imageNode.asyncLayout()
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 5.0), imageSize: dimensions.aspectFilled(contentImageSize), boundingSize: contentImageSize, intrinsicInsets: UIEdgeInsets()))
applyImageLayout()
let imageFrame = CGRect(origin: CGPoint(x: availableSize.width - contentImageSize.width - 10.0 - contextInset, y: floorToScreenPixels((height - contentImageSize.height) / 2.0)), size: contentImageSize)
imageNode.frame = imageFrame
transition.setFrame(view: imageButtonView, frame: imageFrame)
}
} else {
self.imageNode?.removeFromSupernode()
self.imageNode = nil
self.imageButtonView?.removeFromSuperview()
self.imageButtonView = nil
}
if themeUpdated {
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}

View File

@ -2042,3 +2042,310 @@ private func getCachedStory(storyId: StoryId, transaction: Transaction) -> Engin
return nil
}
}
public final class RepostStoriesContentContextImpl: StoryContentContext {
private let context: AccountContext
private let readGlobally: Bool
public private(set) var stateValue: StoryContentContextState?
public var state: Signal<StoryContentContextState, NoError> {
return self.statePromise.get()
}
private let statePromise = Promise<StoryContentContextState>()
private let updatedPromise = Promise<Void>()
public var updated: Signal<Void, NoError> {
return self.updatedPromise.get()
}
private var storyDisposable: Disposable?
private var requestedStoryKeys = Set<StoryKey>()
private var requestStoryDisposables = DisposableSet()
private var currentForwardInfoStories: [StoryId: Promise<EngineStoryItem?>] = [:]
public init(
context: AccountContext,
storyId: StoryId,
storyItems: [(EnginePeer, EngineStoryItem)],
readGlobally: Bool
) {
self.context = context
self.readGlobally = readGlobally
// let item: Signal<Stories.StoredItem?, NoError>
// if let storyItem {
// item = .single(.item(storyItem.asStoryItem()))
// } else {
// item = context.account.postbox.combinedView(keys: [PostboxViewKey.story(id: storyId)])
// |> map { views -> Stories.StoredItem? in
// return (views.views[PostboxViewKey.story(id: storyId)] as? StoryView)?.item?.get(Stories.StoredItem.self)
// }
// }
self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.Presence(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global()
),
.single(0)
// item |> mapToSignal { item -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in
// return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in
// guard let item else {
// return (nil, [:], [:], [:])
// }
// var peers: [PeerId: Peer] = [:]
// var stories: [StoryId: EngineStoryItem?] = [:]
// var allEntityFiles: [MediaId: TelegramMediaFile] = [:]
// if case let .item(item) = item {
// if let views = item.views {
// for id in views.seenPeerIds {
// if let peer = transaction.getPeer(id) {
// peers[peer.id] = peer
// }
// }
// }
// if let forwardInfo = item.forwardInfo, case let .known(peerId, id, _) = forwardInfo {
// if let peer = transaction.getPeer(peerId) {
// peers[peer.id] = peer
// }
// let storyId = StoryId(peerId: peerId, id: id)
// if let story = getCachedStory(storyId: storyId, transaction: transaction) {
// stories[storyId] = story
// } else {
// stories.updateValue(nil, forKey: storyId)
// }
// }
// for entity in item.entities {
// if case let .CustomEmoji(_, fileId) = entity.type {
// let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
// if allEntityFiles[mediaId] == nil {
// if let file = transaction.getMedia(mediaId) as? TelegramMediaFile {
// allEntityFiles[file.fileId] = file
// }
// }
// }
// }
// for mediaArea in item.mediaAreas {
// if case let .reaction(_, reaction, _) = mediaArea {
// if case let .custom(fileId) = reaction {
// let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
// if allEntityFiles[mediaId] == nil {
// if let file = transaction.getMedia(mediaId) as? TelegramMediaFile {
// allEntityFiles[file.fileId] = file
// }
// }
// }
// }
// }
// }
// return (item, peers, allEntityFiles, stories)
// }
// }
)
|> deliverOnMainQueue).startStrict(next: { [weak self] data, skip in
guard let self else {
return
}
let _ = skip
let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings) = data
// let (item, peers, allEntityFiles, forwardInfoStories) = itemAndPeers
guard let peer else {
return
}
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: [])
let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence,
canViewStats: canViewStats
)
if let first = storyItems.first {
let (peer, storyItem) = first
let (nextPeer, nextStoryItem) = storyItems[1]
let mainItem = StoryContentItem(
position: 0,
dayCounters: nil,
peerId: peer.id,
storyItem: storyItem,
entityFiles: [:] //extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
)
let nextItem = StoryContentItem(
position: 0,
dayCounters: nil,
peerId: nextPeer.id,
storyItem: nextStoryItem,
entityFiles: [:] //extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
)
let stateValue = StoryContentContextState(
slice: StoryContentContextState.FocusedSlice(
peer: peer,
additionalPeerData: additionalPeerData,
item: mainItem,
totalCount: 1,
previousItemId: nil,
nextItemId: nil,
allItems: [mainItem],
forwardInfoStories: [:] //self.currentForwardInfoStories
),
previousSlice: nil,
nextSlice: StoryContentContextState.FocusedSlice(
peer: nextPeer,
additionalPeerData: additionalPeerData,
item: nextItem,
totalCount: 1,
previousItemId: nil,
nextItemId: nil,
allItems: [nextItem],
forwardInfoStories: [:] //self.currentForwardInfoStories
)
)
if self.stateValue == nil || self.stateValue?.slice != stateValue.slice {
self.stateValue = stateValue
self.statePromise.set(.single(stateValue))
self.updatedPromise.set(.single(Void()))
}
}
// for (storyId, story) in forwardInfoStories {
// let promise: Promise<EngineStoryItem?>
// var added = false
// if let current = self.currentForwardInfoStories[storyId] {
// promise = current
// } else {
// promise = Promise<EngineStoryItem?>()
// self.currentForwardInfoStories[storyId] = promise
// added = true
// }
// if let story {
// promise.set(.single(story))
// } else if added {
// promise.set(self.context.engine.messages.getStory(peerId: storyId.peerId, id: storyId.id))
// }
// }
// if item == nil {
// let storyKey = StoryKey(peerId: storyId.peerId, id: storyId.id)
// if !self.requestedStoryKeys.contains(storyKey) {
// self.requestedStoryKeys.insert(storyKey)
//
// self.requestStoryDisposables.add(self.context.engine.messages.refreshStories(peerId: storyId.peerId, ids: [storyId.id]).startStrict())
// }
// }
// if let item, case let .item(itemValue) = item, let media = itemValue.media {
// let mappedItem = EngineStoryItem(
// id: itemValue.id,
// timestamp: itemValue.timestamp,
// expirationTimestamp: itemValue.expirationTimestamp,
// media: EngineMedia(media),
// mediaAreas: itemValue.mediaAreas,
// text: itemValue.text,
// entities: itemValue.entities,
// views: itemValue.views.flatMap { views in
// return EngineStoryItem.Views(
// seenCount: views.seenCount,
// reactedCount: views.reactedCount,
// forwardCount: views.forwardCount,
// seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
// return peers[id].flatMap(EnginePeer.init)
// },
// reactions: views.reactions,
// hasList: views.hasList
// )
// },
// privacy: itemValue.privacy.flatMap(EngineStoryPrivacy.init),
// isPinned: itemValue.isPinned,
// isExpired: itemValue.isExpired,
// isPublic: itemValue.isPublic,
// isPending: false,
// isCloseFriends: itemValue.isCloseFriends,
// isContacts: itemValue.isContacts,
// isSelectedContacts: itemValue.isSelectedContacts,
// isForwardingDisabled: itemValue.isForwardingDisabled,
// isEdited: itemValue.isEdited,
// isMy: itemValue.isMy,
// myReaction: itemValue.myReaction,
// forwardInfo: itemValue.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) }
// )
//
// let mainItem = StoryContentItem(
// position: 0,
// dayCounters: nil,
// peerId: peer.id,
// storyItem: mappedItem,
// entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
// )
// let stateValue = StoryContentContextState(
// slice: StoryContentContextState.FocusedSlice(
// peer: peer,
// additionalPeerData: additionalPeerData,
// item: mainItem,
// totalCount: 1,
// previousItemId: nil,
// nextItemId: nil,
// allItems: [mainItem],
// forwardInfoStories: self.currentForwardInfoStories
// ),
// previousSlice: nil,
// nextSlice: nil
// )
//
// if self.stateValue == nil || self.stateValue?.slice != stateValue.slice {
// self.stateValue = stateValue
// self.statePromise.set(.single(stateValue))
// self.updatedPromise.set(.single(Void()))
// }
// } else {
// let stateValue = StoryContentContextState(
// slice: nil,
// previousSlice: nil,
// nextSlice: nil
// )
//
// if self.stateValue == nil || self.stateValue?.slice != stateValue.slice {
// self.stateValue = stateValue
// self.statePromise.set(.single(stateValue))
// self.updatedPromise.set(.single(Void()))
// }
// }
})
}
deinit {
self.storyDisposable?.dispose()
self.requestStoryDisposables.dispose()
}
public func resetSideStates() {
}
public func navigate(navigation: StoryContentContextNavigation) {
}
public func markAsSeen(id: StoryId) {
if self.readGlobally {
if !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
let _ = self.context.engine.messages.markStoryAsSeen(peerId: id.peerId, id: id.id, asPinned: false).startStandalone()
}
}
}
}

View File

@ -435,6 +435,7 @@ public final class StoryItemSetContainerComponent: Component {
var preparingToDisplayViewList: Bool = false
var viewListDisplayState: ViewListDisplayState = .hidden
private var targetViewListDisplayStateIsFull: Bool = false
private var viewListMetrics: (minHeight: CGFloat, maxHeight: CGFloat, currentHeight: CGFloat)?
@ -1231,6 +1232,8 @@ public final class StoryItemSetContainerComponent: Component {
var displayViewLists = false
if component.slice.peer.id == component.context.account.peerId {
displayViewLists = true
} else if case let .channel(channel) = component.slice.peer, channel.flags.contains(.isCreator) || channel.adminRights?.rights.contains(.canPostStories) == true {
displayViewLists = true
}
if displayViewLists {
@ -1912,6 +1915,8 @@ public final class StoryItemSetContainerComponent: Component {
var displayViewLists = false
if component.slice.peer.id == component.context.account.peerId {
displayViewLists = true
} else if case let .channel(channel) = component.slice.peer, channel.flags.contains(.isCreator) || channel.adminRights?.rights.contains(.canPostStories) == true {
displayViewLists = true
}
if displayViewLists {
@ -3123,6 +3128,8 @@ public final class StoryItemSetContainerComponent: Component {
var displayViewLists = false
if component.slice.peer.id == component.context.account.peerId {
displayViewLists = true
} else if case let .channel(channel) = component.slice.peer, channel.flags.contains(.isCreator) || channel.adminRights?.rights.contains(.canPostStories) == true {
displayViewLists = true
}
if displayViewLists, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
@ -3497,6 +3504,12 @@ public final class StoryItemSetContainerComponent: Component {
}
self.openPeerStories(peer: peer, avatarNode: avatarNode)
},
openStory: { [weak self] peer, id, stories, sourceView in
guard let self else {
return
}
self.openReposts(peer: peer, id: id, stories: stories, sourceView: sourceView)
},
openPremiumIntro: { [weak self] in
guard let self else {
return
@ -5151,7 +5164,7 @@ public final class StoryItemSetContainerComponent: Component {
}
}
func openPeerStories(peer: EnginePeer, avatarNode: AvatarNode) {
func openPeerStories(peer: EnginePeer, avatarNode: AvatarNode?) {
guard let component = self.component else {
return
}
@ -5162,6 +5175,75 @@ public final class StoryItemSetContainerComponent: Component {
StoryContainerScreen.openPeerStories(context: component.context, peerId: peer.id, parentController: controller, avatarNode: avatarNode)
}
func openReposts(peer: EnginePeer, id: Int32, stories: [(EnginePeer, EngineStoryItem)], sourceView: UIView) {
guard let component = self.component else {
return
}
guard let controller = component.controller() else {
return
}
let context = component.context
let storyContent = RepostStoriesContentContextImpl(context: context, storyId: StoryId(peerId: peer.id, id: id), storyItems: stories, readGlobally: false)
let _ = (storyContent.state
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak controller, weak sourceView] _ in
guard let controller, let sourceView else {
return
}
let transitionIn = StoryContainerScreen.TransitionIn(
sourceView: sourceView,
sourceRect: sourceView.bounds,
sourceCornerRadius: sourceView.bounds.width * 0.5,
sourceIsAvatar: false
)
let storyContainerScreen = StoryContainerScreen(
context: context,
content: storyContent,
transitionIn: transitionIn,
transitionOut: { [weak sourceView] peerId, storyIdValue in
if let sourceView {
let destinationView = sourceView
return StoryContainerScreen.TransitionOut(
destinationView: destinationView,
transitionView: StoryContainerScreen.TransitionView(
makeView: { [weak destinationView] in
let parentView = UIView()
if let copyView = destinationView?.snapshotContentTree(unhide: true) {
parentView.addSubview(copyView)
}
return parentView
},
updateView: { copyView, state, transition in
guard let view = copyView.subviews.first else {
return
}
let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
transition.setScale(view: view, scale: size.width / state.destinationSize.width)
},
insertCloneTransitionView: nil
),
destinationRect: destinationView.bounds,
destinationCornerRadius: destinationView.bounds.width * 0.5,
destinationIsAvatar: false,
completed: { [weak sourceView] in
guard let sourceView else {
return
}
sourceView.isHidden = false
}
)
} else {
return nil
}
}
)
controller.push(storyContainerScreen)
})
}
private let updateDisposable = MetaDisposable()
func openStoryEditing(repost: Bool = false) {
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {

View File

@ -71,6 +71,7 @@ final class StoryItemSetViewListComponent: Component {
let openPeer: (EnginePeer) -> Void
let peerContextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void
let openPeerStories: (EnginePeer, AvatarNode) -> Void
let openStory: (EnginePeer, Int32, [(EnginePeer, EngineStoryItem)], UIView) -> Void
let openPremiumIntro: () -> Void
let setIsSearchActive: (Bool) -> Void
let controller: () -> ViewController?
@ -95,6 +96,7 @@ final class StoryItemSetViewListComponent: Component {
openPeer: @escaping (EnginePeer) -> Void,
peerContextAction: @escaping (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void,
openPeerStories: @escaping (EnginePeer, AvatarNode) -> Void,
openStory: @escaping (EnginePeer, Int32, [(EnginePeer, EngineStoryItem)], UIView) -> Void,
openPremiumIntro: @escaping () -> Void,
setIsSearchActive: @escaping (Bool) -> Void,
controller: @escaping () -> ViewController?
@ -118,6 +120,7 @@ final class StoryItemSetViewListComponent: Component {
self.openPeer = openPeer
self.peerContextAction = peerContextAction
self.openPeerStories = openPeerStories
self.openStory = openStory
self.openPremiumIntro = openPremiumIntro
self.setIsSearchActive = setIsSearchActive
self.controller = controller
@ -257,7 +260,7 @@ final class StoryItemSetViewListComponent: Component {
let measureItem = ComponentView<Empty>()
var placeholderImage: UIImage?
var visibleItems: [EnginePeer.Id: ComponentView<Empty>] = [:]
var visibleItems: [EngineStoryViewListContext.Item.ItemHash: ComponentView<Empty>] = [:]
var visiblePlaceholderViews: [Int: UIImageView] = [:]
var emptyIcon: ComponentView<Empty>?
@ -388,7 +391,7 @@ final class StoryItemSetViewListComponent: Component {
synchronousLoad = hint.synchronousLoad
}
var validIds: [EnginePeer.Id] = []
var validIds: [EngineStoryViewListContext.Item.ItemHash] = []
var validPlaceholderIds: [Int] = []
if let range = itemLayout.visibleItems(for: visibleBounds) {
for index in range.lowerBound ..< range.upperBound {
@ -432,21 +435,21 @@ final class StoryItemSetViewListComponent: Component {
var itemTransition = transition.withUserData(PeerListItemComponent.TransitionHint(synchronousLoad: true))
let item = viewListState.items[index]
validIds.append(item.peer.id)
validIds.append(item.uniqueId)
let visibleItem: ComponentView<Empty>
if let current = self.visibleItems[item.peer.id] {
if let current = self.visibleItems[item.uniqueId] {
visibleItem = current
} else {
if !transition.animation.isImmediate {
itemTransition = .immediate
}
visibleItem = ComponentView()
self.visibleItems[item.peer.id] = visibleItem
self.visibleItems[item.uniqueId] = visibleItem
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let dateText = humanReadableStringForTimestamp(strings: component.strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: item.timestamp, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
var dateText = humanReadableStringForTimestamp(strings: component.strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: item.timestamp, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
dateFormatString: { value in
return PresentationStrings.FormattedString(string: component.strings.Chat_MessageSeenTimestamp_Date(value).string, ranges: [])
},
@ -461,6 +464,10 @@ final class StoryItemSetViewListComponent: Component {
}
)).string
if let story = item.story, !story.text.isEmpty {
dateText += " • commented"
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
@ -473,7 +480,7 @@ final class StoryItemSetViewListComponent: Component {
peer: item.peer,
storyStats: item.storyStats,
subtitle: dateText,
subtitleAccessory: .checks,
subtitleAccessory: item.story != nil ? .repost : .checks,
presence: nil,
reaction: item.reaction.flatMap { reaction -> PeerListItemComponent.Reaction in
var animationFileId: Int64?
@ -490,7 +497,9 @@ final class StoryItemSetViewListComponent: Component {
}
case let .custom(fileId):
animationFileId = fileId
animationFile = item.reactionFile
if case let .view(view) = item {
animationFile = view.reactionFile
}
}
return PeerListItemComponent.Reaction(
reaction: reaction,
@ -498,6 +507,7 @@ final class StoryItemSetViewListComponent: Component {
animationFileId: animationFileId
)
},
story: item.story,
selectionState: .none,
hasNext: index != viewListState.totalCount - 1 || itemLayout.premiumFooterSize != nil,
action: { [weak self] peer in
@ -514,6 +524,19 @@ final class StoryItemSetViewListComponent: Component {
return
}
component.openPeerStories(peer, avatarNode)
},
openStory: { [weak self] peer, id, sourceView in
guard let self, let component = self.component, let state = self.viewListState else {
return
}
var stories: [(EnginePeer, EngineStoryItem)] = []
for item in state.items {
if let story = item.story {
stories.append((item.peer, story))
}
}
component.openStory(peer, id, stories, sourceView)
}
)),
environment: {},
@ -534,7 +557,7 @@ final class StoryItemSetViewListComponent: Component {
}
}
var removeIds: [EnginePeer.Id] = []
var removeIds: [EngineStoryViewListContext.Item.ItemHash] = []
for (id, visibleItem) in self.visibleItems {
if !validIds.contains(id) {
removeIds.append(id)
@ -1292,25 +1315,26 @@ final class StoryItemSetViewListComponent: Component {
var items: [ContextMenuItem] = []
let sortMode = self.sortMode
// items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortReposts, icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/Repost"), color: theme.contextMenu.primaryColor)
// }, additionalLeftIcon: { theme in
// if sortMode != .repostsFirst {
// return nil
// }
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, a in
// a(.default)
//
// guard let self else {
// return
// }
// if self.sortMode != .repostsFirst {
// self.sortMode = .repostsFirst
// self.state?.updated(transition: .immediate)
// }
// })))
if component.peerId.isGroupOrChannel {
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortReposts, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/Repost"), color: theme.contextMenu.primaryColor)
}, additionalLeftIcon: { theme in
if sortMode != .repostsFirst {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
if self.sortMode != .repostsFirst {
self.sortMode = .repostsFirst
self.state?.updated(transition: .immediate)
}
})))
}
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortReactions, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.contextMenu.primaryColor)
}, additionalLeftIcon: { theme in
@ -1531,7 +1555,9 @@ final class StoryItemSetViewListComponent: Component {
}
let titleText: String
if let totalCount = currentTotalCount, let currentTotalReactionCount {
if component.peerId.isGroupOrChannel {
titleText = component.strings.Story_ViewList_TitleReactions
} else if let totalCount = currentTotalCount, let currentTotalReactionCount {
if totalCount > 0 && totalCount > currentTotalReactionCount {
titleText = component.strings.Story_ViewList_ViewerCount(Int32(totalCount))
} else {
@ -1583,11 +1609,11 @@ final class StoryItemSetViewListComponent: Component {
var displaySearchBar = false
var displaySortSelector = false
if !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
if component.peerId == component.context.account.peerId && !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
} else {
if let views = component.storyItem.views, views.hasList {
if let views = component.storyItem.views, views.hasList || component.peerId.isGroupOrChannel {
if let totalCount = currentTotalCount {
if totalCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
if !component.peerId.isGroupOrChannel, totalCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
displayModeSelector = true
displaySearchBar = true
}

View File

@ -740,7 +740,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .full:
break
}
} else if let _ = media as? TelegramMediaGiveaway {
} else if media is TelegramMediaGiveaway || media is TelegramMediaGiveawayResults {
let progress = params.progress
let presentationData = strongSelf.presentationData

View File

@ -160,6 +160,9 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo
} else if let _ = media as? TelegramMediaGiveaway {
hasUneditableAttributes = true
break
} else if let _ = media as? TelegramMediaGiveawayResults {
hasUneditableAttributes = true
break
}
}