mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Various improvements
This commit is contained in:
parent
14064f94eb
commit
2962851527
@ -14202,3 +14202,21 @@ Sorry for the inconvenience.";
|
|||||||
"Story.Privacy.KeepOnMyPageManyInfo" = "Keep these stories on your profile even after they expire in %@. Privacy settings will apply.";
|
"Story.Privacy.KeepOnMyPageManyInfo" = "Keep these stories on your profile even after they expire in %@. Privacy settings will apply.";
|
||||||
"Story.Privacy.KeepOnChannelPageManyInfo" = "Keep these stories on the channel profile even after they expire in %@.";
|
"Story.Privacy.KeepOnChannelPageManyInfo" = "Keep these stories on the channel profile even after they expire in %@.";
|
||||||
"Story.Privacy.KeepOnGroupPageManyInfo" = "Keep these stories on the group page even after they expire in %@.";
|
"Story.Privacy.KeepOnGroupPageManyInfo" = "Keep these stories on the group page even after they expire in %@.";
|
||||||
|
|
||||||
|
"Gift.Options.Gift.Filter.Resale" = "Resale";
|
||||||
|
"Gift.Options.Gift.Resale" = "resale";
|
||||||
|
|
||||||
|
"Stars.Intro.Transaction.GiftPurchase" = "Gift Purchase";
|
||||||
|
"Stars.Intro.Transaction.GiftSale" = "Gift Sale";
|
||||||
|
|
||||||
|
"Stars.Transaction.GiftPurchase" = "Gift Purchase";
|
||||||
|
"Stars.Transaction.GiftSale" = "Gift Sale";
|
||||||
|
|
||||||
|
"Channel.Info.AutoTranslate" = "Auto-Translate Messages";
|
||||||
|
|
||||||
|
"ChannelBoost.Table.AutoTranslate" = "Autotranslation of Messages";
|
||||||
|
"ChannelBoost.AutoTranslate" = "Autotranslation of Messages";
|
||||||
|
"ChannelBoost.AutoTranslateLevelText" = "Your channel needs **Level %1$@** to enable autotranslation of messages.";
|
||||||
|
|
||||||
|
"Channel.AdminLog.MessageToggleAutoTranslateOn" = "%@ enabled autotranslation of messages";
|
||||||
|
"Channel.AdminLog.MessageToggleAutoTranslateOff" = "%@ disabled autotranslation of messages";
|
||||||
|
|||||||
@ -125,6 +125,7 @@ public enum BoostSubject: Equatable {
|
|||||||
case emojiPack
|
case emojiPack
|
||||||
case noAds
|
case noAds
|
||||||
case wearGift
|
case wearGift
|
||||||
|
case autoTranslate
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StarsPurchasePurpose: Equatable {
|
public enum StarsPurchasePurpose: Equatable {
|
||||||
@ -164,6 +165,7 @@ public struct PremiumConfiguration {
|
|||||||
minChannelCustomWallpaperLevel: 10,
|
minChannelCustomWallpaperLevel: 10,
|
||||||
minChannelRestrictAdsLevel: 50,
|
minChannelRestrictAdsLevel: 50,
|
||||||
minChannelWearGiftLevel: 8,
|
minChannelWearGiftLevel: 8,
|
||||||
|
minChannelAutoTranslateLevel: 3,
|
||||||
minGroupProfileIconLevel: 7,
|
minGroupProfileIconLevel: 7,
|
||||||
minGroupEmojiStatusLevel: 8,
|
minGroupEmojiStatusLevel: 8,
|
||||||
minGroupWallpaperLevel: 9,
|
minGroupWallpaperLevel: 9,
|
||||||
@ -193,6 +195,7 @@ public struct PremiumConfiguration {
|
|||||||
public let minChannelCustomWallpaperLevel: Int32
|
public let minChannelCustomWallpaperLevel: Int32
|
||||||
public let minChannelRestrictAdsLevel: Int32
|
public let minChannelRestrictAdsLevel: Int32
|
||||||
public let minChannelWearGiftLevel: Int32
|
public let minChannelWearGiftLevel: Int32
|
||||||
|
public let minChannelAutoTranslateLevel: Int32
|
||||||
public let minGroupProfileIconLevel: Int32
|
public let minGroupProfileIconLevel: Int32
|
||||||
public let minGroupEmojiStatusLevel: Int32
|
public let minGroupEmojiStatusLevel: Int32
|
||||||
public let minGroupWallpaperLevel: Int32
|
public let minGroupWallpaperLevel: Int32
|
||||||
@ -221,6 +224,7 @@ public struct PremiumConfiguration {
|
|||||||
minChannelCustomWallpaperLevel: Int32,
|
minChannelCustomWallpaperLevel: Int32,
|
||||||
minChannelRestrictAdsLevel: Int32,
|
minChannelRestrictAdsLevel: Int32,
|
||||||
minChannelWearGiftLevel: Int32,
|
minChannelWearGiftLevel: Int32,
|
||||||
|
minChannelAutoTranslateLevel: Int32,
|
||||||
minGroupProfileIconLevel: Int32,
|
minGroupProfileIconLevel: Int32,
|
||||||
minGroupEmojiStatusLevel: Int32,
|
minGroupEmojiStatusLevel: Int32,
|
||||||
minGroupWallpaperLevel: Int32,
|
minGroupWallpaperLevel: Int32,
|
||||||
@ -248,6 +252,7 @@ public struct PremiumConfiguration {
|
|||||||
self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel
|
self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel
|
||||||
self.minChannelRestrictAdsLevel = minChannelRestrictAdsLevel
|
self.minChannelRestrictAdsLevel = minChannelRestrictAdsLevel
|
||||||
self.minChannelWearGiftLevel = minChannelWearGiftLevel
|
self.minChannelWearGiftLevel = minChannelWearGiftLevel
|
||||||
|
self.minChannelAutoTranslateLevel = minChannelAutoTranslateLevel
|
||||||
self.minGroupProfileIconLevel = minGroupProfileIconLevel
|
self.minGroupProfileIconLevel = minGroupProfileIconLevel
|
||||||
self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel
|
self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel
|
||||||
self.minGroupWallpaperLevel = minGroupWallpaperLevel
|
self.minGroupWallpaperLevel = minGroupWallpaperLevel
|
||||||
@ -283,6 +288,7 @@ public struct PremiumConfiguration {
|
|||||||
minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel,
|
minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel,
|
||||||
minChannelRestrictAdsLevel: get(data["channel_restrict_sponsored_level_min"]) ?? defaultValue.minChannelRestrictAdsLevel,
|
minChannelRestrictAdsLevel: get(data["channel_restrict_sponsored_level_min"]) ?? defaultValue.minChannelRestrictAdsLevel,
|
||||||
minChannelWearGiftLevel: get(data["channel_emoji_status_level_min"]) ?? defaultValue.minChannelWearGiftLevel,
|
minChannelWearGiftLevel: get(data["channel_emoji_status_level_min"]) ?? defaultValue.minChannelWearGiftLevel,
|
||||||
|
minChannelAutoTranslateLevel: get(data["channel_autotranslation_level_min"]) ?? defaultValue.minChannelAutoTranslateLevel,
|
||||||
minGroupProfileIconLevel: get(data["group_profile_bg_icon_level_min"]) ?? defaultValue.minGroupProfileIconLevel,
|
minGroupProfileIconLevel: get(data["group_profile_bg_icon_level_min"]) ?? defaultValue.minGroupProfileIconLevel,
|
||||||
minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel,
|
minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel,
|
||||||
minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel,
|
minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel,
|
||||||
|
|||||||
@ -61,6 +61,8 @@ func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: Acco
|
|||||||
return configuration.minChannelRestrictAdsLevel
|
return configuration.minChannelRestrictAdsLevel
|
||||||
case .wearGift:
|
case .wearGift:
|
||||||
return configuration.minChannelWearGiftLevel
|
return configuration.minChannelWearGiftLevel
|
||||||
|
case .autoTranslate:
|
||||||
|
return configuration.minChannelAutoTranslateLevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +245,7 @@ private final class LevelSectionComponent: CombinedComponent {
|
|||||||
case emojiPack
|
case emojiPack
|
||||||
case noAds
|
case noAds
|
||||||
case wearGift
|
case wearGift
|
||||||
|
case autoTranslate
|
||||||
|
|
||||||
func title(strings: PresentationStrings, isGroup: Bool) -> String {
|
func title(strings: PresentationStrings, isGroup: Bool) -> String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -274,6 +277,8 @@ private final class LevelSectionComponent: CombinedComponent {
|
|||||||
return strings.ChannelBoost_Table_NoAds
|
return strings.ChannelBoost_Table_NoAds
|
||||||
case .wearGift:
|
case .wearGift:
|
||||||
return strings.ChannelBoost_Table_WearGift
|
return strings.ChannelBoost_Table_WearGift
|
||||||
|
case .autoTranslate:
|
||||||
|
return strings.ChannelBoost_Table_AutoTranslate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +312,8 @@ private final class LevelSectionComponent: CombinedComponent {
|
|||||||
return "Premium/BoostPerk/NoAds"
|
return "Premium/BoostPerk/NoAds"
|
||||||
case .wearGift:
|
case .wearGift:
|
||||||
return "Premium/BoostPerk/NoAds"
|
return "Premium/BoostPerk/NoAds"
|
||||||
|
case .autoTranslate:
|
||||||
|
return "Chat/Title Panels/Translate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -647,6 +654,8 @@ private final class SheetContent: CombinedComponent {
|
|||||||
textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string
|
textString = strings.ChannelBoost_EnableNoAdsLevelText("\(requiredLevel)").string
|
||||||
case .wearGift:
|
case .wearGift:
|
||||||
textString = strings.ChannelBoost_WearGiftLevelText("\(requiredLevel)").string
|
textString = strings.ChannelBoost_WearGiftLevelText("\(requiredLevel)").string
|
||||||
|
case .autoTranslate:
|
||||||
|
textString = strings.ChannelBoost_AutoTranslateLevelText("\(requiredLevel)").string
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
|
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
|
||||||
@ -1162,6 +1171,9 @@ private final class SheetContent: CombinedComponent {
|
|||||||
if !isGroup && level >= requiredBoostSubjectLevel(subject: .noAds, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
if !isGroup && level >= requiredBoostSubjectLevel(subject: .noAds, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||||
perks.append(.noAds)
|
perks.append(.noAds)
|
||||||
}
|
}
|
||||||
|
if !isGroup && level >= requiredBoostSubjectLevel(subject: .autoTranslate, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||||
|
perks.append(.autoTranslate)
|
||||||
|
}
|
||||||
// if !isGroup && level >= requiredBoostSubjectLevel(subject: .wearGift, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
// if !isGroup && level >= requiredBoostSubjectLevel(subject: .wearGift, group: isGroup, context: component.context, configuration: premiumConfiguration) {
|
||||||
// perks.append(.wearGift)
|
// perks.append(.wearGift)
|
||||||
// }
|
// }
|
||||||
@ -1466,6 +1478,8 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
|
|||||||
titleString = strings.ChannelBoost_NoAds
|
titleString = strings.ChannelBoost_NoAds
|
||||||
case .wearGift:
|
case .wearGift:
|
||||||
titleString = strings.ChannelBoost_WearGift
|
titleString = strings.ChannelBoost_WearGift
|
||||||
|
case .autoTranslate:
|
||||||
|
titleString = strings.ChannelBoost_AutoTranslate
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current
|
titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current
|
||||||
|
|||||||
@ -171,6 +171,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[589338437] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStartGroupCall($0) }
|
dict[589338437] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStartGroupCall($0) }
|
||||||
dict[-1895328189] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStopPoll($0) }
|
dict[-1895328189] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStopPoll($0) }
|
||||||
dict[1693675004] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleAntiSpam($0) }
|
dict[1693675004] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleAntiSpam($0) }
|
||||||
|
dict[-988285058] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleAutotranslation($0) }
|
||||||
dict[46949251] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleForum($0) }
|
dict[46949251] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleForum($0) }
|
||||||
dict[1456906823] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleGroupCallSetting($0) }
|
dict[1456906823] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleGroupCallSetting($0) }
|
||||||
dict[460916654] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleInvites($0) }
|
dict[460916654] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleInvites($0) }
|
||||||
@ -1461,6 +1462,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[276907596] = { return Api.storage.FileType.parse_fileWebp($0) }
|
dict[276907596] = { return Api.storage.FileType.parse_fileWebp($0) }
|
||||||
dict[1862033025] = { return Api.stories.AllStories.parse_allStories($0) }
|
dict[1862033025] = { return Api.stories.AllStories.parse_allStories($0) }
|
||||||
dict[291044926] = { return Api.stories.AllStories.parse_allStoriesNotModified($0) }
|
dict[291044926] = { return Api.stories.AllStories.parse_allStoriesNotModified($0) }
|
||||||
|
dict[-1014513586] = { return Api.stories.CanSendStoryCount.parse_canSendStoryCount($0) }
|
||||||
dict[-488736969] = { return Api.stories.FoundStories.parse_foundStories($0) }
|
dict[-488736969] = { return Api.stories.FoundStories.parse_foundStories($0) }
|
||||||
dict[-890861720] = { return Api.stories.PeerStories.parse_peerStories($0) }
|
dict[-890861720] = { return Api.stories.PeerStories.parse_peerStories($0) }
|
||||||
dict[1673780490] = { return Api.stories.Stories.parse_stories($0) }
|
dict[1673780490] = { return Api.stories.Stories.parse_stories($0) }
|
||||||
@ -2592,6 +2594,8 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.stories.AllStories:
|
case let _1 as Api.stories.AllStories:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.stories.CanSendStoryCount:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.stories.FoundStories:
|
case let _1 as Api.stories.FoundStories:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.stories.PeerStories:
|
case let _1 as Api.stories.PeerStories:
|
||||||
|
|||||||
@ -605,6 +605,7 @@ public extension Api {
|
|||||||
case channelAdminLogEventActionStartGroupCall(call: Api.InputGroupCall)
|
case channelAdminLogEventActionStartGroupCall(call: Api.InputGroupCall)
|
||||||
case channelAdminLogEventActionStopPoll(message: Api.Message)
|
case channelAdminLogEventActionStopPoll(message: Api.Message)
|
||||||
case channelAdminLogEventActionToggleAntiSpam(newValue: Api.Bool)
|
case channelAdminLogEventActionToggleAntiSpam(newValue: Api.Bool)
|
||||||
|
case channelAdminLogEventActionToggleAutotranslation(newValue: Api.Bool)
|
||||||
case channelAdminLogEventActionToggleForum(newValue: Api.Bool)
|
case channelAdminLogEventActionToggleForum(newValue: Api.Bool)
|
||||||
case channelAdminLogEventActionToggleGroupCallSetting(joinMuted: Api.Bool)
|
case channelAdminLogEventActionToggleGroupCallSetting(joinMuted: Api.Bool)
|
||||||
case channelAdminLogEventActionToggleInvites(newValue: Api.Bool)
|
case channelAdminLogEventActionToggleInvites(newValue: Api.Bool)
|
||||||
@ -897,6 +898,12 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
newValue.serialize(buffer, true)
|
newValue.serialize(buffer, true)
|
||||||
break
|
break
|
||||||
|
case .channelAdminLogEventActionToggleAutotranslation(let newValue):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-988285058)
|
||||||
|
}
|
||||||
|
newValue.serialize(buffer, true)
|
||||||
|
break
|
||||||
case .channelAdminLogEventActionToggleForum(let newValue):
|
case .channelAdminLogEventActionToggleForum(let newValue):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(46949251)
|
buffer.appendInt32(46949251)
|
||||||
@ -1039,6 +1046,8 @@ public extension Api {
|
|||||||
return ("channelAdminLogEventActionStopPoll", [("message", message as Any)])
|
return ("channelAdminLogEventActionStopPoll", [("message", message as Any)])
|
||||||
case .channelAdminLogEventActionToggleAntiSpam(let newValue):
|
case .channelAdminLogEventActionToggleAntiSpam(let newValue):
|
||||||
return ("channelAdminLogEventActionToggleAntiSpam", [("newValue", newValue as Any)])
|
return ("channelAdminLogEventActionToggleAntiSpam", [("newValue", newValue as Any)])
|
||||||
|
case .channelAdminLogEventActionToggleAutotranslation(let newValue):
|
||||||
|
return ("channelAdminLogEventActionToggleAutotranslation", [("newValue", newValue as Any)])
|
||||||
case .channelAdminLogEventActionToggleForum(let newValue):
|
case .channelAdminLogEventActionToggleForum(let newValue):
|
||||||
return ("channelAdminLogEventActionToggleForum", [("newValue", newValue as Any)])
|
return ("channelAdminLogEventActionToggleForum", [("newValue", newValue as Any)])
|
||||||
case .channelAdminLogEventActionToggleGroupCallSetting(let joinMuted):
|
case .channelAdminLogEventActionToggleGroupCallSetting(let joinMuted):
|
||||||
@ -1677,6 +1686,19 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_channelAdminLogEventActionToggleAutotranslation(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
||||||
|
var _1: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleAutotranslation(newValue: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
public static func parse_channelAdminLogEventActionToggleForum(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
public static func parse_channelAdminLogEventActionToggleForum(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
||||||
var _1: Api.Bool?
|
var _1: Api.Bool?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
|
|||||||
@ -760,6 +760,42 @@ public extension Api.stories {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.stories {
|
||||||
|
enum CanSendStoryCount: TypeConstructorDescription {
|
||||||
|
case canSendStoryCount(countRemains: Int32)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .canSendStoryCount(let countRemains):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1014513586)
|
||||||
|
}
|
||||||
|
serializeInt32(countRemains, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .canSendStoryCount(let countRemains):
|
||||||
|
return ("canSendStoryCount", [("countRemains", countRemains as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_canSendStoryCount(_ reader: BufferReader) -> CanSendStoryCount? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.stories.CanSendStoryCount.canSendStoryCount(countRemains: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.stories {
|
public extension Api.stories {
|
||||||
enum FoundStories: TypeConstructorDescription {
|
enum FoundStories: TypeConstructorDescription {
|
||||||
case foundStories(flags: Int32, count: Int32, stories: [Api.FoundStory], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
|
case foundStories(flags: Int32, count: Int32, stories: [Api.FoundStory], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
|
||||||
@ -1560,55 +1596,3 @@ public extension Api.updates {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.updates {
|
|
||||||
enum State: TypeConstructorDescription {
|
|
||||||
case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32)
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .state(let pts, let qts, let date, let seq, let unreadCount):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(-1519637954)
|
|
||||||
}
|
|
||||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
|
||||||
serializeInt32(qts, buffer: buffer, boxed: false)
|
|
||||||
serializeInt32(date, buffer: buffer, boxed: false)
|
|
||||||
serializeInt32(seq, buffer: buffer, boxed: false)
|
|
||||||
serializeInt32(unreadCount, buffer: buffer, boxed: false)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .state(let pts, let qts, let date, let seq, let unreadCount):
|
|
||||||
return ("state", [("pts", pts as Any), ("qts", qts as Any), ("date", date as Any), ("seq", seq as Any), ("unreadCount", unreadCount as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_state(_ reader: BufferReader) -> State? {
|
|
||||||
var _1: Int32?
|
|
||||||
_1 = reader.readInt32()
|
|
||||||
var _2: Int32?
|
|
||||||
_2 = reader.readInt32()
|
|
||||||
var _3: Int32?
|
|
||||||
_3 = reader.readInt32()
|
|
||||||
var _4: Int32?
|
|
||||||
_4 = reader.readInt32()
|
|
||||||
var _5: Int32?
|
|
||||||
_5 = reader.readInt32()
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = _2 != nil
|
|
||||||
let _c3 = _3 != nil
|
|
||||||
let _c4 = _4 != nil
|
|
||||||
let _c5 = _5 != nil
|
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
|
||||||
return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,55 @@
|
|||||||
|
public extension Api.updates {
|
||||||
|
enum State: TypeConstructorDescription {
|
||||||
|
case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .state(let pts, let qts, let date, let seq, let unreadCount):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1519637954)
|
||||||
|
}
|
||||||
|
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(qts, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(date, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(seq, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(unreadCount, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .state(let pts, let qts, let date, let seq, let unreadCount):
|
||||||
|
return ("state", [("pts", pts as Any), ("qts", qts as Any), ("date", date as Any), ("seq", seq as Any), ("unreadCount", unreadCount as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_state(_ reader: BufferReader) -> State? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Int32?
|
||||||
|
_2 = reader.readInt32()
|
||||||
|
var _3: Int32?
|
||||||
|
_3 = reader.readInt32()
|
||||||
|
var _4: Int32?
|
||||||
|
_4 = reader.readInt32()
|
||||||
|
var _5: Int32?
|
||||||
|
_5 = reader.readInt32()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
let _c4 = _4 != nil
|
||||||
|
let _c5 = _5 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||||
|
return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.upload {
|
public extension Api.upload {
|
||||||
enum CdnFile: TypeConstructorDescription {
|
enum CdnFile: TypeConstructorDescription {
|
||||||
case cdnFile(bytes: Buffer)
|
case cdnFile(bytes: Buffer)
|
||||||
|
|||||||
@ -3590,6 +3590,22 @@ public extension Api.functions.channels {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.channels {
|
||||||
|
static func toggleAutotranslation(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(377471137)
|
||||||
|
channel.serialize(buffer, true)
|
||||||
|
enabled.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "channels.toggleAutotranslation", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels {
|
public extension Api.functions.channels {
|
||||||
static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -11139,15 +11155,15 @@ public extension Api.functions.stories {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.stories {
|
public extension Api.functions.stories {
|
||||||
static func canSendStory(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
static func canSendStory(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.CanSendStoryCount>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-941629475)
|
buffer.appendInt32(820732912)
|
||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
return (FunctionDescription(name: "stories.canSendStory", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
return (FunctionDescription(name: "stories.canSendStory", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.CanSendStoryCount? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.Bool?
|
var result: Api.stories.CanSendStoryCount?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
result = Api.parse(reader, signature: signature) as? Api.stories.CanSendStoryCount
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1702,7 +1702,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum StoriesUploadAvailability {
|
public enum StoriesUploadAvailability {
|
||||||
case available
|
case available(remainingCount: Int32)
|
||||||
case weeklyLimit
|
case weeklyLimit
|
||||||
case monthlyLimit
|
case monthlyLimit
|
||||||
case expiringLimit
|
case expiringLimit
|
||||||
@ -1729,10 +1729,9 @@ func _internal_checkStoriesUploadAvailability(account: Account, target: Stories.
|
|||||||
|
|
||||||
return account.network.request(Api.functions.stories.canSendStory(peer: inputPeer))
|
return account.network.request(Api.functions.stories.canSendStory(peer: inputPeer))
|
||||||
|> map { result -> StoriesUploadAvailability in
|
|> map { result -> StoriesUploadAvailability in
|
||||||
if result == .boolTrue {
|
switch result {
|
||||||
return .available
|
case let .canSendStoryCount(countRemains):
|
||||||
} else {
|
return .available(remainingCount: countRemains)
|
||||||
return .unknownLimit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> `catch` { error -> Signal<StoriesUploadAvailability, NoError> in
|
|> `catch` { error -> Signal<StoriesUploadAvailability, NoError> in
|
||||||
|
|||||||
@ -179,6 +179,7 @@ public enum BotPaymentFormRequestError {
|
|||||||
case alreadyActive
|
case alreadyActive
|
||||||
case noPaymentNeeded
|
case noPaymentNeeded
|
||||||
case disallowedStarGift
|
case disallowedStarGift
|
||||||
|
case starGiftResellTooEarly(Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BotPaymentInvoice {
|
extension BotPaymentInvoice {
|
||||||
@ -482,6 +483,11 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw
|
|||||||
return .fail(.noPaymentNeeded)
|
return .fail(.noPaymentNeeded)
|
||||||
} else if error.errorDescription == "USER_DISALLOWED_STARGIFTS" {
|
} else if error.errorDescription == "USER_DISALLOWED_STARGIFTS" {
|
||||||
return .fail(.disallowedStarGift)
|
return .fail(.disallowedStarGift)
|
||||||
|
} else if error.errorDescription.hasPrefix("STARGIFT_RESELL_TOO_EARLY_") {
|
||||||
|
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "STARGIFT_RESELL_TOO_EARLY_".count)...])
|
||||||
|
if let value = Int32(timeout) {
|
||||||
|
return .fail(.starGiftResellTooEarly(value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -847,8 +847,14 @@ public enum TransferStarGiftError {
|
|||||||
|
|
||||||
public enum BuyStarGiftError {
|
public enum BuyStarGiftError {
|
||||||
case generic
|
case generic
|
||||||
|
case starGiftResellTooEarly(Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum UpdateStarGiftPriceError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum UpgradeStarGiftError {
|
public enum UpgradeStarGiftError {
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
@ -858,7 +864,12 @@ func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id
|
|||||||
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { error -> Signal<BotPaymentForm?, BuyStarGiftError> in
|
|> `catch` { error -> Signal<BotPaymentForm?, BuyStarGiftError> in
|
||||||
return .fail(.generic)
|
switch error {
|
||||||
|
case let .starGiftResellTooEarly(value):
|
||||||
|
return .fail(.starGiftResellTooEarly(value))
|
||||||
|
default:
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { paymentForm in
|
|> mapToSignal { paymentForm in
|
||||||
if let paymentForm {
|
if let paymentForm {
|
||||||
@ -1487,7 +1498,13 @@ private final class ProfileGiftsContextImpl {
|
|||||||
}
|
}
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
disposable.set(
|
disposable.set(
|
||||||
_internal_upgradeStarGift(account: self.account, formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo).startStrict(next: { [weak self] result in
|
(_internal_upgradeStarGift(
|
||||||
|
account: self.account,
|
||||||
|
formId: formId,
|
||||||
|
reference: reference,
|
||||||
|
keepOriginalInfo: keepOriginalInfo
|
||||||
|
)
|
||||||
|
|> deliverOn(self.queue)).startStrict(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1509,39 +1526,54 @@ private final class ProfileGiftsContextImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStarGiftResellPrice(reference: StarGiftReference, price: Int64?) {
|
func updateStarGiftResellPrice(reference: StarGiftReference, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||||
self.actionDisposable.set(
|
return Signal { [weak self] subscriber in
|
||||||
_internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price).startStrict()
|
guard let self else {
|
||||||
)
|
return EmptyDisposable
|
||||||
|
|
||||||
|
|
||||||
if let index = self.gifts.firstIndex(where: { gift in
|
|
||||||
if gift.reference == reference {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}) {
|
|
||||||
if case let .unique(uniqueGift) = self.gifts[index].gift {
|
|
||||||
let updatedUniqueGift = uniqueGift.withResellStars(price)
|
|
||||||
let updatedGift = self.gifts[index].withGift(.unique(updatedUniqueGift))
|
|
||||||
self.gifts[index] = updatedGift
|
|
||||||
}
|
}
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
disposable.set(
|
||||||
|
(_internal_updateStarGiftResalePrice(
|
||||||
|
account: self.account,
|
||||||
|
reference: reference,
|
||||||
|
price: price
|
||||||
|
)
|
||||||
|
|> deliverOn(self.queue)).startStrict(error: { error in
|
||||||
|
subscriber.putError(error)
|
||||||
|
}, completed: {
|
||||||
|
if let index = self.gifts.firstIndex(where: { gift in
|
||||||
|
if gift.reference == reference {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}) {
|
||||||
|
if case let .unique(uniqueGift) = self.gifts[index].gift {
|
||||||
|
let updatedUniqueGift = uniqueGift.withResellStars(price)
|
||||||
|
let updatedGift = self.gifts[index].withGift(.unique(updatedUniqueGift))
|
||||||
|
self.gifts[index] = updatedGift
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = self.filteredGifts.firstIndex(where: { gift in
|
||||||
|
if gift.reference == reference {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}) {
|
||||||
|
if case let .unique(uniqueGift) = self.filteredGifts[index].gift {
|
||||||
|
let updatedUniqueGift = uniqueGift.withResellStars(price)
|
||||||
|
let updatedGift = self.filteredGifts[index].withGift(.unique(updatedUniqueGift))
|
||||||
|
self.filteredGifts[index] = updatedGift
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pushState()
|
||||||
|
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return disposable
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = self.filteredGifts.firstIndex(where: { gift in
|
|
||||||
if gift.reference == reference {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}) {
|
|
||||||
if case let .unique(uniqueGift) = self.filteredGifts[index].gift {
|
|
||||||
let updatedUniqueGift = uniqueGift.withResellStars(price)
|
|
||||||
let updatedGift = self.filteredGifts[index].withGift(.unique(updatedUniqueGift))
|
|
||||||
self.filteredGifts[index] = updatedGift
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pushState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleStarGiftsNotifications(enabled: Bool) {
|
func toggleStarGiftsNotifications(enabled: Bool) {
|
||||||
@ -1939,9 +1971,17 @@ public final class ProfileGiftsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateStarGiftResellPrice(reference: StarGiftReference, price: Int64?) {
|
public func updateStarGiftResellPrice(reference: StarGiftReference, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||||
self.impl.with { impl in
|
return Signal { subscriber in
|
||||||
impl.updateStarGiftResellPrice(reference: reference, price: price)
|
let disposable = MetaDisposable()
|
||||||
|
self.impl.with { impl in
|
||||||
|
disposable.set(impl.updateStarGiftResellPrice(reference: reference, price: price).start(error: { error in
|
||||||
|
subscriber.putError(error)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2274,23 +2314,21 @@ func _internal_toggleStarGiftsNotifications(account: Account, peerId: EnginePeer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_updateStarGiftResalePrice(account: Account, reference: StarGiftReference, price: Int64?) -> Signal<Never, NoError> {
|
func _internal_updateStarGiftResalePrice(account: Account, reference: StarGiftReference, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||||
return account.postbox.transaction { transaction in
|
return account.postbox.transaction { transaction in
|
||||||
return reference.apiStarGiftReference(transaction: transaction)
|
return reference.apiStarGiftReference(transaction: transaction)
|
||||||
}
|
}
|
||||||
|
|> castError(UpdateStarGiftPriceError.self)
|
||||||
|> mapToSignal { starGift in
|
|> mapToSignal { starGift in
|
||||||
guard let starGift else {
|
guard let starGift else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.payments.updateStarGiftPrice(stargift: starGift, resellStars: price ?? 0))
|
return account.network.request(Api.functions.payments.updateStarGiftPrice(stargift: starGift, resellStars: price ?? 0))
|
||||||
|> map(Optional.init)
|
|> mapError { error -> UpdateStarGiftPriceError in
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
return .generic
|
||||||
return .single(nil)
|
|
||||||
}
|
}
|
||||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
|> mapToSignal { updates -> Signal<Void, UpdateStarGiftPriceError> in
|
||||||
if let updates {
|
account.stateManager.addUpdates(updates)
|
||||||
account.stateManager.addUpdates(updates)
|
|
||||||
}
|
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
@ -2496,6 +2534,66 @@ private final class ResaleGiftsContextImpl {
|
|||||||
|
|
||||||
self.loadMore()
|
self.loadMore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||||
|
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
|
||||||
|
|> afterCompleted { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.queue.async {
|
||||||
|
if let count = self.count {
|
||||||
|
self.count = max(0, count - 1)
|
||||||
|
}
|
||||||
|
self.gifts.removeAll(where: { gift in
|
||||||
|
if case let .unique(uniqueGift) = gift, uniqueGift.slug == slug {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
self.pushState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStarGiftResellPrice(slug: String, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||||
|
return Signal { [weak self] subscriber in
|
||||||
|
guard let self else {
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
disposable.set(
|
||||||
|
(_internal_updateStarGiftResalePrice(
|
||||||
|
account: self.account,
|
||||||
|
reference: .slug(slug: slug),
|
||||||
|
price: price
|
||||||
|
)
|
||||||
|
|> deliverOn(self.queue)).startStrict(error: { error in
|
||||||
|
subscriber.putError(error)
|
||||||
|
}, completed: {
|
||||||
|
if let index = self.gifts.firstIndex(where: { gift in
|
||||||
|
if case let .unique(uniqueGift) = gift, uniqueGift.slug == slug {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}) {
|
||||||
|
if let price {
|
||||||
|
if case let .unique(uniqueGift) = self.gifts[index] {
|
||||||
|
self.gifts[index] = .unique(uniqueGift.withResellStars(price))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.gifts.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pushState()
|
||||||
|
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func pushState() {
|
private func pushState() {
|
||||||
let state = ResaleGiftsContext.State(
|
let state = ResaleGiftsContext.State(
|
||||||
@ -2584,6 +2682,34 @@ public final class ResaleGiftsContext {
|
|||||||
impl.updateFilterAttributes(attributes)
|
impl.updateFilterAttributes(attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.impl.with { impl in
|
||||||
|
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId).start(error: { error in
|
||||||
|
subscriber.putError(error)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateStarGiftResellPrice(slug: String, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.impl.with { impl in
|
||||||
|
disposable.set(impl.updateStarGiftResellPrice(slug: slug, price: price).start(error: { error in
|
||||||
|
subscriber.putError(error)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var currentState: ResaleGiftsContext.State? {
|
public var currentState: ResaleGiftsContext.State? {
|
||||||
var state: ResaleGiftsContext.State?
|
var state: ResaleGiftsContext.State?
|
||||||
|
|||||||
@ -153,7 +153,7 @@ public extension TelegramEngine {
|
|||||||
return _internal_toggleStarGiftsNotifications(account: self.account, peerId: peerId, enabled: enabled)
|
return _internal_toggleStarGiftsNotifications(account: self.account, peerId: peerId, enabled: enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateStarGiftResalePrice(reference: StarGiftReference, price: Int64?) -> Signal<Never, NoError> {
|
public func updateStarGiftResalePrice(reference: StarGiftReference, price: Int64?) -> Signal<Never, UpdateStarGiftPriceError> {
|
||||||
return _internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price)
|
return _internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,6 +94,7 @@ public enum AdminLogEventAction {
|
|||||||
case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?)
|
case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?)
|
||||||
case changeEmojiPack(prev: StickerPackReference?, new: StickerPackReference?)
|
case changeEmojiPack(prev: StickerPackReference?, new: StickerPackReference?)
|
||||||
case participantSubscriptionExtended(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
|
case participantSubscriptionExtended(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
|
||||||
|
case toggleAutoTranslation(Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChannelAdminLogEventError {
|
public enum ChannelAdminLogEventError {
|
||||||
@ -457,6 +458,8 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||||||
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
||||||
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||||
}
|
}
|
||||||
|
case let .channelAdminLogEventActionToggleAutotranslation(newValue):
|
||||||
|
action = .toggleAutoTranslation(boolFromApiValue(newValue))
|
||||||
}
|
}
|
||||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||||
if let action = action {
|
if let action = action {
|
||||||
|
|||||||
@ -3469,7 +3469,11 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
}
|
}
|
||||||
self.postingAvailabilityDisposable = (self.postingAvailabilityPromise.get()
|
self.postingAvailabilityDisposable = (self.postingAvailabilityPromise.get()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] availability in
|
|> deliverOnMainQueue).start(next: { [weak self] availability in
|
||||||
guard let self, availability != .available else {
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case let .available(remainingCount) = availability {
|
||||||
|
let _ = remainingCount
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.node.postingAvailable = false
|
self.node.postingAvailable = false
|
||||||
|
|||||||
@ -2282,6 +2282,33 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||||
return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||||
|
case let .toggleAutoTranslation(value):
|
||||||
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
|
var author: Peer?
|
||||||
|
if let peer = self.entry.peers[self.entry.event.peerId] {
|
||||||
|
author = peer
|
||||||
|
peers[peer.id] = peer
|
||||||
|
}
|
||||||
|
var text: String = ""
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
|
if value {
|
||||||
|
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageToggleAutoTranslateOn(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
|
||||||
|
if index == 0, let author = author {
|
||||||
|
return [.TextMention(peerId: author.id)]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}, to: &text, entities: &entities)
|
||||||
|
} else {
|
||||||
|
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageToggleAutoTranslateOff(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
|
||||||
|
if index == 0, let author = author {
|
||||||
|
return [.TextMention(peerId: author.id)]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}, to: &text, entities: &entities)
|
||||||
|
}
|
||||||
|
let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil)
|
||||||
|
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||||
|
return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -370,18 +370,10 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
var isSoldOut = false
|
var isSoldOut = false
|
||||||
switch gift {
|
switch gift {
|
||||||
case let .generic(gift):
|
case let .generic(gift):
|
||||||
if let availability = gift.availability, availability.resale > 0 {
|
if let _ = gift.soldOut {
|
||||||
//TODO:localize
|
|
||||||
//TODO:unmock
|
|
||||||
ribbon = GiftItemComponent.Ribbon(
|
|
||||||
text: "resale",
|
|
||||||
color: .green
|
|
||||||
)
|
|
||||||
} else if let _ = gift.soldOut {
|
|
||||||
if let availability = gift.availability, availability.resale > 0 {
|
if let availability = gift.availability, availability.resale > 0 {
|
||||||
//TODO:localize
|
|
||||||
ribbon = GiftItemComponent.Ribbon(
|
ribbon = GiftItemComponent.Ribbon(
|
||||||
text: "resale",
|
text: environment.strings.Gift_Options_Gift_Resale,
|
||||||
color: .green
|
color: .green
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -415,7 +407,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
let subject: GiftItemComponent.Subject
|
let subject: GiftItemComponent.Subject
|
||||||
switch gift {
|
switch gift {
|
||||||
case let .generic(gift):
|
case let .generic(gift):
|
||||||
if let availability = gift.availability, let minResaleStars = availability.minResaleStars {
|
if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars {
|
||||||
subject = .starGift(gift: gift, price: "⭐️ \(minResaleStars)+")
|
subject = .starGift(gift: gift, price: "⭐️ \(minResaleStars)+")
|
||||||
} else {
|
} else {
|
||||||
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
||||||
@ -450,7 +442,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
mainController = controller
|
mainController = controller
|
||||||
}
|
}
|
||||||
if case let .generic(gift) = gift {
|
if case let .generic(gift) = gift {
|
||||||
if let availability = gift.availability, availability.remains == 0 || (availability.resale > 0) {
|
if let availability = gift.availability, availability.remains == 0 {
|
||||||
if availability.resale > 0 {
|
if availability.resale > 0 {
|
||||||
let storeController = component.context.sharedContext.makeGiftStoreController(
|
let storeController = component.context.sharedContext.makeGiftStoreController(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
@ -1296,7 +1288,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
starsAmountsSet.insert(gift.price)
|
starsAmountsSet.insert(gift.price)
|
||||||
if let availability = gift.availability {
|
if let availability = gift.availability {
|
||||||
hasLimited = true
|
hasLimited = true
|
||||||
if availability.resale > 0 {
|
if availability.remains == 0 && availability.resale > 0 {
|
||||||
hasResale = true
|
hasResale = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1317,10 +1309,9 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
))
|
))
|
||||||
|
|
||||||
if hasResale {
|
if hasResale {
|
||||||
//TODO:localize
|
|
||||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||||
id: AnyHashable(StarsFilter.resale.rawValue),
|
id: AnyHashable(StarsFilter.resale.rawValue),
|
||||||
title: "Resale"
|
title: strings.Gift_Options_Gift_Filter_Resale
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -82,6 +82,7 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
|
|
||||||
private let navigationTitle = ComponentView<Empty>()
|
private let navigationTitle = ComponentView<Empty>()
|
||||||
private let remainingCount = ComponentView<Empty>()
|
private let remainingCount = ComponentView<Empty>()
|
||||||
|
private let resaleSection = ComponentView<Empty>()
|
||||||
private let introContent = ComponentView<Empty>()
|
private let introContent = ComponentView<Empty>()
|
||||||
private let introSection = ComponentView<Empty>()
|
private let introSection = ComponentView<Empty>()
|
||||||
private let starsSection = ComponentView<Empty>()
|
private let starsSection = ComponentView<Empty>()
|
||||||
@ -787,6 +788,59 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
contentHeight += sectionSpacing
|
contentHeight += sectionSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case let .starGift(starGift, _) = component.subject, let availability = starGift.availability, availability.resale > 0 {
|
||||||
|
let resaleSectionSize = self.resaleSection.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(ListSectionComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
header: nil,
|
||||||
|
footer: nil,
|
||||||
|
items: [
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
title: AnyComponent(VStack([
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(
|
||||||
|
MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "Available for Resale", font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
], alignment: .left, spacing: 2.0)),
|
||||||
|
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: presentationStringsFormattedNumber(Int32(availability.resale), environment.dateTimeFormat.groupingSeparator),
|
||||||
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: environment.theme.list.itemSecondaryTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
))), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0))),
|
||||||
|
action: { [weak self] _ in
|
||||||
|
guard let self, let component = self.component, let controller = environment.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let storeController = component.context.sharedContext.makeGiftStoreController(
|
||||||
|
context: component.context,
|
||||||
|
peerId: component.peerId,
|
||||||
|
gift: starGift
|
||||||
|
)
|
||||||
|
controller.push(storeController)
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||||
|
)
|
||||||
|
let resaleSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: resaleSectionSize)
|
||||||
|
if let resaleSectionView = self.resaleSection.view {
|
||||||
|
if resaleSectionView.superview == nil {
|
||||||
|
self.scrollView.addSubview(resaleSectionView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: resaleSectionView, frame: resaleSectionFrame)
|
||||||
|
}
|
||||||
|
contentHeight += resaleSectionSize.height
|
||||||
|
contentHeight += sectionSpacing
|
||||||
|
}
|
||||||
|
|
||||||
let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||||
|
|||||||
@ -139,21 +139,10 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
self.updateScrolling(interactive: true, transition: self.nextScrollTransition ?? .immediate)
|
self.updateScrolling(interactive: true, transition: self.nextScrollTransition ?? .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var removedStarGifts = Set<String>()
|
|
||||||
private var currentGifts: ([StarGift], Set<String>, Set<String>, Set<String>)?
|
private var currentGifts: ([StarGift], Set<String>, Set<String>, Set<String>)?
|
||||||
private var effectiveGifts: [StarGift]? {
|
private var effectiveGifts: [StarGift]? {
|
||||||
if let gifts = self.state?.starGiftsState?.gifts {
|
if let gifts = self.state?.starGiftsState?.gifts {
|
||||||
if !self.removedStarGifts.isEmpty {
|
return gifts
|
||||||
return gifts.filter { gift in
|
|
||||||
if case let .unique(uniqueGift) = gift {
|
|
||||||
return !self.removedStarGifts.contains(uniqueGift.slug)
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return gifts
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -253,15 +242,14 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
let giftController = GiftViewScreen(
|
let giftController = GiftViewScreen(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
subject: .uniqueGift(uniqueGift, state.peerId)
|
subject: .uniqueGift(uniqueGift, state.peerId),
|
||||||
)
|
buyGift: { slug, peerId in
|
||||||
giftController.onBuySuccess = { [weak self] in
|
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId) ?? .complete()
|
||||||
guard let self else {
|
},
|
||||||
return
|
updateResellStars: { price in
|
||||||
|
return self.state?.starGiftsContext.updateStarGiftResellPrice(slug: uniqueGift.slug, price: price) ?? .complete()
|
||||||
}
|
}
|
||||||
self.removedStarGifts.insert(uniqueGift.slug)
|
)
|
||||||
self.state?.updated(transition: .spring(duration: 0.3))
|
|
||||||
}
|
|
||||||
mainController.push(giftController)
|
mainController.push(giftController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -507,15 +495,17 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
items.append(.custom(SearchContextItem(
|
if modelAttributes.count >= 8 {
|
||||||
context: component.context,
|
items.append(.custom(SearchContextItem(
|
||||||
placeholder: "Search",
|
context: component.context,
|
||||||
value: "",
|
placeholder: "Search",
|
||||||
valueChanged: { value in
|
value: "",
|
||||||
searchQueryPromise.set(value)
|
valueChanged: { value in
|
||||||
}
|
searchQueryPromise.set(value)
|
||||||
), false))
|
}
|
||||||
items.append(.separator)
|
), false))
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
items.append(.custom(GiftAttributeListContextItem(
|
items.append(.custom(GiftAttributeListContextItem(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
attributes: modelAttributes,
|
attributes: modelAttributes,
|
||||||
@ -597,15 +587,17 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
items.append(.custom(SearchContextItem(
|
if backdropAttributes.count >= 8 {
|
||||||
context: component.context,
|
items.append(.custom(SearchContextItem(
|
||||||
placeholder: "Search",
|
context: component.context,
|
||||||
value: "",
|
placeholder: "Search",
|
||||||
valueChanged: { value in
|
value: "",
|
||||||
searchQueryPromise.set(value)
|
valueChanged: { value in
|
||||||
}
|
searchQueryPromise.set(value)
|
||||||
), false))
|
}
|
||||||
items.append(.separator)
|
), false))
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
items.append(.custom(GiftAttributeListContextItem(
|
items.append(.custom(GiftAttributeListContextItem(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
attributes: backdropAttributes,
|
attributes: backdropAttributes,
|
||||||
@ -687,15 +679,17 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
items.append(.custom(SearchContextItem(
|
if patternAttributes.count >= 8 {
|
||||||
context: component.context,
|
items.append(.custom(SearchContextItem(
|
||||||
placeholder: "Search",
|
context: component.context,
|
||||||
value: "",
|
placeholder: "Search",
|
||||||
valueChanged: { value in
|
value: "",
|
||||||
searchQueryPromise.set(value)
|
valueChanged: { value in
|
||||||
}
|
searchQueryPromise.set(value)
|
||||||
), false))
|
}
|
||||||
items.append(.separator)
|
), false))
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
items.append(.custom(GiftAttributeListContextItem(
|
items.append(.custom(GiftAttributeListContextItem(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
attributes: patternAttributes,
|
attributes: patternAttributes,
|
||||||
|
|||||||
@ -460,8 +460,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.onBuySuccess()
|
|
||||||
|
|
||||||
self.inProgress = false
|
self.inProgress = false
|
||||||
|
|
||||||
var animationFile: TelegramMediaFile?
|
var animationFile: TelegramMediaFile?
|
||||||
@ -2902,7 +2900,6 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
let updateSubject = ActionSlot<GiftViewScreen.Subject>()
|
let updateSubject = ActionSlot<GiftViewScreen.Subject>()
|
||||||
|
|
||||||
public var disposed: () -> Void = {}
|
public var disposed: () -> Void = {}
|
||||||
public var onBuySuccess: () -> Void = {}
|
|
||||||
|
|
||||||
fileprivate var showBalance = false {
|
fileprivate var showBalance = false {
|
||||||
didSet {
|
didSet {
|
||||||
@ -2922,7 +2919,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
transferGift: ((Bool, EnginePeer.Id) -> Signal<Never, TransferStarGiftError>)? = nil,
|
transferGift: ((Bool, EnginePeer.Id) -> Signal<Never, TransferStarGiftError>)? = nil,
|
||||||
upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil,
|
upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil,
|
||||||
buyGift: ((String, EnginePeer.Id) -> Signal<Never, BuyStarGiftError>)? = nil,
|
buyGift: ((String, EnginePeer.Id) -> Signal<Never, BuyStarGiftError>)? = nil,
|
||||||
updateResellStars: ((Int64?) -> Void)? = nil,
|
updateResellStars: ((Int64?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil,
|
||||||
togglePinnedToTop: ((Bool) -> Bool)? = nil,
|
togglePinnedToTop: ((Bool) -> Bool)? = nil,
|
||||||
shareStory: ((StarGift.UniqueGift) -> Void)? = nil
|
shareStory: ((StarGift.UniqueGift) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
@ -3413,6 +3410,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||||
|
let reference = arguments.reference ?? .slug(slug: gift.slug)
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
if let resellStars = gift.resellStars, resellStars > 0, !update {
|
if let resellStars = gift.resellStars, resellStars > 0, !update {
|
||||||
@ -3425,44 +3423,39 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let _ = ((updateResellStars?(nil) ?? context.engine.payments.updateStarGiftResalePrice(reference: reference, price: nil))
|
||||||
switch self.subject {
|
|> deliverOnMainQueue).startStandalone(error: { error in
|
||||||
case let .profileGift(peerId, currentSubject):
|
|
||||||
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(nil))))
|
}, completed: {
|
||||||
case let .uniqueGift(_, recipientPeerId):
|
switch self.subject {
|
||||||
self.subject = .uniqueGift(gift.withResellStars(nil), recipientPeerId)
|
case let .profileGift(peerId, currentSubject):
|
||||||
default:
|
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(nil))))
|
||||||
break
|
case let .uniqueGift(_, recipientPeerId):
|
||||||
}
|
self.subject = .uniqueGift(gift.withResellStars(nil), recipientPeerId)
|
||||||
self.onBuySuccess()
|
default:
|
||||||
|
break
|
||||||
let text = "\(giftTitle) is removed from sale."
|
|
||||||
let tooltipController = UndoOverlayController(
|
|
||||||
presentationData: presentationData,
|
|
||||||
content: .universalImage(
|
|
||||||
image: generateTintedImage(image: UIImage(bundleImageName: "Premium/Collectible/Unlist"), color: .white)!,
|
|
||||||
size: nil,
|
|
||||||
title: nil,
|
|
||||||
text: text,
|
|
||||||
customUndoText: nil,
|
|
||||||
timeout: 3.0
|
|
||||||
),
|
|
||||||
position: .bottom,
|
|
||||||
animateInAsReplacement: false,
|
|
||||||
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
|
|
||||||
action: { action in
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
self.present(tooltipController, in: .window(.root))
|
let text = "\(giftTitle) is removed from sale."
|
||||||
|
let tooltipController = UndoOverlayController(
|
||||||
if let updateResellStars {
|
presentationData: presentationData,
|
||||||
updateResellStars(nil)
|
content: .universalImage(
|
||||||
} else {
|
image: generateTintedImage(image: UIImage(bundleImageName: "Premium/Collectible/Unlist"), color: .white)!,
|
||||||
let reference = arguments.reference ?? .slug(slug: gift.slug)
|
size: nil,
|
||||||
let _ = (context.engine.payments.updateStarGiftResalePrice(reference: reference, price: nil)
|
title: nil,
|
||||||
|> deliverOnMainQueue).startStandalone()
|
text: text,
|
||||||
}
|
customUndoText: nil,
|
||||||
|
timeout: 3.0
|
||||||
|
),
|
||||||
|
position: .bottom,
|
||||||
|
animateInAsReplacement: false,
|
||||||
|
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
|
||||||
|
action: { action in
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.present(tooltipController, in: .window(.root))
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||||
})
|
})
|
||||||
@ -3476,46 +3469,47 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self.subject {
|
let _ = ((updateResellStars?(price) ?? context.engine.payments.updateStarGiftResalePrice(reference: reference, price: price))
|
||||||
case let .profileGift(peerId, currentSubject):
|
|> deliverOnMainQueue).startStandalone(error: { error in
|
||||||
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(price))))
|
|
||||||
case let .uniqueGift(_, recipientPeerId):
|
}, completed: { [weak self] in
|
||||||
self.subject = .uniqueGift(gift.withResellStars(price), recipientPeerId)
|
guard let self else {
|
||||||
default:
|
return
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = "\(giftTitle) is now for sale!"
|
|
||||||
if update {
|
|
||||||
text = "\(giftTitle) is relisted for \(presentationStringsFormattedNumber(Int32(price), presentationData.dateTimeFormat.groupingSeparator)) Stars."
|
|
||||||
}
|
|
||||||
|
|
||||||
let tooltipController = UndoOverlayController(
|
|
||||||
presentationData: presentationData,
|
|
||||||
content: .universalImage(
|
|
||||||
image: generateTintedImage(image: UIImage(bundleImageName: "Premium/Collectible/Sell"), color: .white)!,
|
|
||||||
size: nil,
|
|
||||||
title: nil,
|
|
||||||
text: text,
|
|
||||||
customUndoText: nil,
|
|
||||||
timeout: 3.0
|
|
||||||
),
|
|
||||||
position: .bottom,
|
|
||||||
animateInAsReplacement: false,
|
|
||||||
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
|
|
||||||
action: { action in
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
self.present(tooltipController, in: .window(.root))
|
switch self.subject {
|
||||||
|
case let .profileGift(peerId, currentSubject):
|
||||||
if let updateResellStars {
|
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(price))))
|
||||||
updateResellStars(price)
|
case let .uniqueGift(_, recipientPeerId):
|
||||||
} else {
|
self.subject = .uniqueGift(gift.withResellStars(price), recipientPeerId)
|
||||||
let reference = arguments.reference ?? .slug(slug: gift.slug)
|
default:
|
||||||
let _ = (context.engine.payments.updateStarGiftResalePrice(reference: reference, price: price)
|
break
|
||||||
|> deliverOnMainQueue).startStandalone()
|
}
|
||||||
}
|
|
||||||
|
var text = "\(giftTitle) is now for sale!"
|
||||||
|
if update {
|
||||||
|
text = "\(giftTitle) is relisted for \(presentationStringsFormattedNumber(Int32(price), presentationData.dateTimeFormat.groupingSeparator)) Stars."
|
||||||
|
}
|
||||||
|
|
||||||
|
let tooltipController = UndoOverlayController(
|
||||||
|
presentationData: presentationData,
|
||||||
|
content: .universalImage(
|
||||||
|
image: generateTintedImage(image: UIImage(bundleImageName: "Premium/Collectible/Sell"), color: .white)!,
|
||||||
|
size: nil,
|
||||||
|
title: nil,
|
||||||
|
text: text,
|
||||||
|
customUndoText: nil,
|
||||||
|
timeout: 3.0
|
||||||
|
),
|
||||||
|
position: .bottom,
|
||||||
|
animateInAsReplacement: false,
|
||||||
|
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
|
||||||
|
action: { action in
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.present(tooltipController, in: .window(.root))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
self.push(resellController)
|
self.push(resellController)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -352,6 +352,8 @@ public final class MediaEditor {
|
|||||||
return state.position
|
return state.position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var maxDuration: Double = 60.0
|
||||||
|
|
||||||
public var duration: Double? {
|
public var duration: Double? {
|
||||||
if let stickerEntity = self.stickerEntity {
|
if let stickerEntity = self.stickerEntity {
|
||||||
@ -360,7 +362,7 @@ public final class MediaEditor {
|
|||||||
if let trimRange = self.values.videoTrimRange {
|
if let trimRange = self.values.videoTrimRange {
|
||||||
return trimRange.upperBound - trimRange.lowerBound
|
return trimRange.upperBound - trimRange.lowerBound
|
||||||
} else {
|
} else {
|
||||||
return min(60.0, self.playerPlaybackState.duration)
|
return min(self.maxDuration, self.playerPlaybackState.duration)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@ -369,7 +371,7 @@ public final class MediaEditor {
|
|||||||
|
|
||||||
public var mainVideoDuration: Double? {
|
public var mainVideoDuration: Double? {
|
||||||
if self.player != nil {
|
if self.player != nil {
|
||||||
return min(60.0, self.playerPlaybackState.duration)
|
return min(self.maxDuration, self.playerPlaybackState.duration)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -377,7 +379,7 @@ public final class MediaEditor {
|
|||||||
|
|
||||||
public var additionalVideoDuration: Double? {
|
public var additionalVideoDuration: Double? {
|
||||||
if let additionalPlayer = self.additionalPlayers.first {
|
if let additionalPlayer = self.additionalPlayers.first {
|
||||||
return min(60.0, additionalPlayer.currentItem?.asset.duration.seconds ?? 0.0)
|
return min(self.maxDuration, additionalPlayer.currentItem?.asset.duration.seconds ?? 0.0)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -385,7 +387,15 @@ public final class MediaEditor {
|
|||||||
|
|
||||||
public var originalDuration: Double? {
|
public var originalDuration: Double? {
|
||||||
if self.player != nil || !self.additionalPlayers.isEmpty {
|
if self.player != nil || !self.additionalPlayers.isEmpty {
|
||||||
return min(60.0, self.playerPlaybackState.duration)
|
return self.playerPlaybackState.duration
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var originalCappedDuration: Double? {
|
||||||
|
if self.player != nil || !self.additionalPlayers.isEmpty {
|
||||||
|
return min(self.maxDuration, self.playerPlaybackState.duration)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -909,7 +909,7 @@ public final class MediaEditorValues: Codable, Equatable {
|
|||||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
|
public func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
|
||||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -327,7 +327,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
private let switchCameraButton = ComponentView<Empty>()
|
private let switchCameraButton = ComponentView<Empty>()
|
||||||
|
|
||||||
private let selectionButton = ComponentView<Empty>()
|
private let selectionButton = ComponentView<Empty>()
|
||||||
private let selectionPanel = ComponentView<Empty>()
|
private var selectionPanel: ComponentView<Empty>?
|
||||||
|
|
||||||
private let textCancelButton = ComponentView<Empty>()
|
private let textCancelButton = ComponentView<Empty>()
|
||||||
private let textDoneButton = ComponentView<Empty>()
|
private let textDoneButton = ComponentView<Empty>()
|
||||||
@ -577,6 +577,11 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
view.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.selectionButton.view {
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: view.alpha, duration: 0.2)
|
||||||
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,14 +594,14 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setScale(view: view, scale: 0.1)
|
transition.setScale(view: view, scale: 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttons = [
|
let toolbarButtons = [
|
||||||
self.drawButton,
|
self.drawButton,
|
||||||
self.textButton,
|
self.textButton,
|
||||||
self.stickerButton,
|
self.stickerButton,
|
||||||
self.toolsButton
|
self.toolsButton
|
||||||
]
|
]
|
||||||
|
|
||||||
for button in buttons {
|
for button in toolbarButtons {
|
||||||
if let view = button.view {
|
if let view = button.view {
|
||||||
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||||
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
@ -617,19 +622,17 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.saveButton.view {
|
let topButtons = [
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
self.saveButton,
|
||||||
transition.setScale(view: view, scale: 0.1)
|
self.muteButton,
|
||||||
}
|
self.playbackButton
|
||||||
|
]
|
||||||
|
|
||||||
if let view = self.muteButton.view {
|
for button in topButtons {
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
if let view = button.view {
|
||||||
transition.setScale(view: view, scale: 0.1)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
}
|
transition.setScale(view: view, scale: 0.1)
|
||||||
|
}
|
||||||
if let view = self.playbackButton.view {
|
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
|
||||||
transition.setScale(view: view, scale: 0.1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.scrubber?.view {
|
if let view = self.scrubber?.view {
|
||||||
@ -638,35 +641,30 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.undoButton.view {
|
let stickerButtons = [
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
self.undoButton,
|
||||||
transition.setScale(view: view, scale: 0.1)
|
self.eraseButton,
|
||||||
}
|
self.restoreButton,
|
||||||
|
self.outlineButton,
|
||||||
|
self.cutoutButton
|
||||||
|
]
|
||||||
|
|
||||||
if let view = self.eraseButton.view {
|
for button in stickerButtons {
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
if let view = button.view {
|
||||||
transition.setScale(view: view, scale: 0.1)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
}
|
transition.setScale(view: view, scale: 0.1)
|
||||||
|
}
|
||||||
if let view = self.restoreButton.view {
|
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
|
||||||
transition.setScale(view: view, scale: 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let view = self.outlineButton.view {
|
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
|
||||||
transition.setScale(view: view, scale: 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let view = self.cutoutButton.view {
|
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
|
||||||
transition.setScale(view: view, scale: 0.1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let view = self.textSize.view {
|
if let view = self.textSize.view {
|
||||||
transition.setAlpha(view: view, alpha: 0.0)
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
transition.setScale(view: view, scale: 0.1)
|
transition.setScale(view: view, scale: 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let view = self.selectionButton.view {
|
||||||
|
transition.setAlpha(view: view, alpha: 0.0)
|
||||||
|
transition.setScale(view: view, scale: 0.1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOutToTool(inPlace: Bool, transition: ComponentTransition) {
|
func animateOutToTool(inPlace: Bool, transition: ComponentTransition) {
|
||||||
@ -2000,135 +1998,6 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setScale(view: switchCameraButtonView, scale: isRecordingAdditionalVideo ? 1.0 : 0.01)
|
transition.setScale(view: switchCameraButtonView, scale: isRecordingAdditionalVideo ? 1.0 : 0.01)
|
||||||
transition.setAlpha(view: switchCameraButtonView, alpha: isRecordingAdditionalVideo ? 1.0 : 0.0)
|
transition.setAlpha(view: switchCameraButtonView, alpha: isRecordingAdditionalVideo ? 1.0 : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if controller.node.items.count > 1 {
|
|
||||||
let selectionButtonSize = self.selectionButton.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(PlainButtonComponent(
|
|
||||||
content: AnyComponent(
|
|
||||||
SelectionPanelButtonContentComponent(
|
|
||||||
count: Int32(controller.node.items.count(where: { $0.isEnabled })),
|
|
||||||
isSelected: self.isSelectionPanelOpen,
|
|
||||||
tag: nil
|
|
||||||
)
|
|
||||||
),
|
|
||||||
effectAlignment: .center,
|
|
||||||
action: { [weak self, weak controller] in
|
|
||||||
if let self, let controller {
|
|
||||||
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
|
||||||
if let mediaEditor = controller.node.mediaEditor {
|
|
||||||
if self.isSelectionPanelOpen {
|
|
||||||
mediaEditor.maybePauseVideo()
|
|
||||||
} else {
|
|
||||||
Queue.mainQueue().after(0.1) {
|
|
||||||
mediaEditor.maybeUnpauseVideo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.state?.updated()
|
|
||||||
|
|
||||||
controller.hapticFeedback.impact(.light)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
animateAlpha: false
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
|
||||||
)
|
|
||||||
let selectionButtonFrame = CGRect(
|
|
||||||
origin: CGPoint(x: availableSize.width - selectionButtonSize.width - 12.0, y: inputPanelFrame.minY - selectionButtonSize.height - 3.0),
|
|
||||||
size: selectionButtonSize
|
|
||||||
)
|
|
||||||
if let selectionButtonView = self.selectionButton.view as? PlainButtonComponent.View {
|
|
||||||
if selectionButtonView.superview == nil {
|
|
||||||
self.addSubview(selectionButtonView)
|
|
||||||
}
|
|
||||||
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
|
||||||
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
|
||||||
transition.setScale(view: selectionButtonView, scale: displayTopButtons && !isRecordingAdditionalVideo ? 1.0 : 0.01)
|
|
||||||
transition.setAlpha(view: selectionButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities && !isRecordingAdditionalVideo ? 1.0 : 0.0)
|
|
||||||
|
|
||||||
if self.isSelectionPanelOpen {
|
|
||||||
let selectionPanelFrame = CGRect(
|
|
||||||
origin: CGPoint(x: 12.0, y: inputPanelFrame.minY - selectionButtonSize.height - 3.0 - 130.0),
|
|
||||||
size: CGSize(width: availableSize.width - 24.0, height: 120.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
var selectedItemId = ""
|
|
||||||
if case let .asset(asset) = controller.node.subject {
|
|
||||||
selectedItemId = asset.localIdentifier
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = self.selectionPanel.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(
|
|
||||||
SelectionPanelComponent(
|
|
||||||
previewContainerView: controller.node.previewContentContainerView,
|
|
||||||
frame: selectionPanelFrame,
|
|
||||||
items: controller.node.items,
|
|
||||||
selectedItemId: selectedItemId,
|
|
||||||
itemTapped: { [weak self, weak controller] id in
|
|
||||||
guard let self, let controller else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.isSelectionPanelOpen = false
|
|
||||||
self.state?.updated(transition: id == nil ? .spring(duration: 0.3) : .immediate)
|
|
||||||
|
|
||||||
if let id {
|
|
||||||
controller.node.switchToItem(id)
|
|
||||||
|
|
||||||
controller.hapticFeedback.impact(.light)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
itemSelectionToggled: { [weak self, weak controller] id in
|
|
||||||
guard let self, let controller else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let itemIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == id }) {
|
|
||||||
controller.node.items[itemIndex].isEnabled = !controller.node.items[itemIndex].isEnabled
|
|
||||||
}
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.3))
|
|
||||||
},
|
|
||||||
itemReordered: { [weak self, weak controller] fromId, toId in
|
|
||||||
guard let self, let controller else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let fromIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == fromId }), let toIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == toId }), toIndex < controller.node.items.count else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let fromItem = controller.node.items[fromIndex]
|
|
||||||
let toItem = controller.node.items[toIndex]
|
|
||||||
controller.node.items[fromIndex] = toItem
|
|
||||||
controller.node.items[toIndex] = fromItem
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.3))
|
|
||||||
|
|
||||||
controller.hapticFeedback.tap()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
environment: {},
|
|
||||||
containerSize: availableSize
|
|
||||||
)
|
|
||||||
if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
|
||||||
if selectionPanelView.superview == nil {
|
|
||||||
self.insertSubview(selectionPanelView, belowSubview: selectionButtonView)
|
|
||||||
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
|
||||||
selectionPanelView.animateIn(from: buttonView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectionPanelView.frame = CGRect(origin: .zero, size: availableSize)
|
|
||||||
}
|
|
||||||
} else if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
|
||||||
if !transition.animation.isImmediate, let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
|
||||||
selectionPanelView.animateOut(to: buttonView, completion: { [weak selectionPanelView] in
|
|
||||||
selectionPanelView?.removeFromSuperview()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
selectionPanelView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
inputPanelSize = CGSize(width: 0.0, height: 12.0)
|
inputPanelSize = CGSize(width: 0.0, height: 12.0)
|
||||||
}
|
}
|
||||||
@ -2136,20 +2005,24 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
if case .stickerEditor = controller.mode {
|
if case .stickerEditor = controller.mode {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
var selectionButtonInset: CGFloat = 0.0
|
||||||
|
|
||||||
if let playerState = state.playerState {
|
if let playerState = state.playerState {
|
||||||
let scrubberInset: CGFloat = 9.0
|
let scrubberInset: CGFloat = 9.0
|
||||||
|
|
||||||
let minDuration: Double
|
let minDuration: Double
|
||||||
let maxDuration: Double
|
let maxDuration: Double
|
||||||
|
var segmentDuration: Double?
|
||||||
if playerState.isAudioOnly {
|
if playerState.isAudioOnly {
|
||||||
minDuration = 5.0
|
minDuration = 5.0
|
||||||
maxDuration = 15.0
|
maxDuration = 15.0
|
||||||
} else {
|
} else {
|
||||||
minDuration = 1.0
|
minDuration = 1.0
|
||||||
if case .avatarEditor = controller.mode {
|
if case .avatarEditor = controller.mode {
|
||||||
maxDuration = 10.0
|
maxDuration = 9.9
|
||||||
} else {
|
} else {
|
||||||
maxDuration = storyMaxVideoDuration
|
maxDuration = storyMaxCombinedVideoDuration
|
||||||
|
segmentDuration = storyMaxVideoDuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2224,6 +2097,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
position: playerState.position,
|
position: playerState.position,
|
||||||
minDuration: minDuration,
|
minDuration: minDuration,
|
||||||
maxDuration: maxDuration,
|
maxDuration: maxDuration,
|
||||||
|
segmentDuration: segmentDuration,
|
||||||
isPlaying: playerState.isPlaying,
|
isPlaying: playerState.isPlaying,
|
||||||
tracks: visibleTracks,
|
tracks: visibleTracks,
|
||||||
isCollage: isCollage,
|
isCollage: isCollage,
|
||||||
@ -2363,6 +2237,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let scrubberFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - scrubberSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - scrubberSize.height + controlsBottomInset - inputPanelSize.height + 3.0 - scrubberBottomOffset), size: scrubberSize)
|
let scrubberFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - scrubberSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - scrubberSize.height + controlsBottomInset - inputPanelSize.height + 3.0 - scrubberBottomOffset), size: scrubberSize)
|
||||||
|
selectionButtonInset = scrubberSize.height + 11.0
|
||||||
if let scrubberView = scrubber.view {
|
if let scrubberView = scrubber.view {
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
if scrubberView.superview == nil {
|
if scrubberView.superview == nil {
|
||||||
@ -2407,6 +2282,146 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if controller.node.items.count > 1 {
|
||||||
|
let selectionButtonSize = self.selectionButton.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(PlainButtonComponent(
|
||||||
|
content: AnyComponent(
|
||||||
|
SelectionPanelButtonContentComponent(
|
||||||
|
count: Int32(controller.node.items.count(where: { $0.isEnabled })),
|
||||||
|
isSelected: self.isSelectionPanelOpen,
|
||||||
|
tag: nil
|
||||||
|
)
|
||||||
|
),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: { [weak self, weak controller] in
|
||||||
|
if let self, let controller {
|
||||||
|
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
||||||
|
if let mediaEditor = controller.node.mediaEditor {
|
||||||
|
if self.isSelectionPanelOpen {
|
||||||
|
mediaEditor.maybePauseVideo()
|
||||||
|
} else {
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
|
mediaEditor.maybeUnpauseVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
|
||||||
|
controller.hapticFeedback.impact(.light)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animateAlpha: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 33.0, height: 33.0)
|
||||||
|
)
|
||||||
|
let selectionButtonFrame = CGRect(
|
||||||
|
origin: CGPoint(x: availableSize.width - selectionButtonSize.width - 12.0, y: availableSize.height - environment.safeInsets.bottom - selectionButtonSize.height + controlsBottomInset - inputPanelSize.height - 3.0 - selectionButtonInset),
|
||||||
|
size: selectionButtonSize
|
||||||
|
)
|
||||||
|
if let selectionButtonView = self.selectionButton.view as? PlainButtonComponent.View {
|
||||||
|
if selectionButtonView.superview == nil {
|
||||||
|
self.addSubview(selectionButtonView)
|
||||||
|
}
|
||||||
|
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
||||||
|
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
||||||
|
transition.setScale(view: selectionButtonView, scale: displayTopButtons && !isRecordingAdditionalVideo ? 1.0 : 0.01)
|
||||||
|
transition.setAlpha(view: selectionButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities && !isRecordingAdditionalVideo ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
if self.isSelectionPanelOpen {
|
||||||
|
let selectionPanelFrame = CGRect(
|
||||||
|
origin: CGPoint(x: 12.0, y: selectionButtonFrame.minY - 130.0),
|
||||||
|
size: CGSize(width: availableSize.width - 24.0, height: 120.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
var selectedItemId = ""
|
||||||
|
if case let .asset(asset) = controller.node.subject {
|
||||||
|
selectedItemId = asset.localIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectionPanel: ComponentView<Empty>
|
||||||
|
if let current = self.selectionPanel {
|
||||||
|
selectionPanel = current
|
||||||
|
} else {
|
||||||
|
selectionPanel = ComponentView<Empty>()
|
||||||
|
self.selectionPanel = selectionPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = selectionPanel.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(
|
||||||
|
SelectionPanelComponent(
|
||||||
|
previewContainerView: controller.node.previewContentContainerView,
|
||||||
|
frame: selectionPanelFrame,
|
||||||
|
items: controller.node.items,
|
||||||
|
selectedItemId: selectedItemId,
|
||||||
|
itemTapped: { [weak self, weak controller] id in
|
||||||
|
guard let self, let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.isSelectionPanelOpen = false
|
||||||
|
self.state?.updated(transition: id == nil ? .spring(duration: 0.3) : .immediate)
|
||||||
|
|
||||||
|
if let id {
|
||||||
|
controller.node.switchToItem(id)
|
||||||
|
|
||||||
|
controller.hapticFeedback.impact(.light)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemSelectionToggled: { [weak self, weak controller] id in
|
||||||
|
guard let self, let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let itemIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == id }) {
|
||||||
|
controller.node.items[itemIndex].isEnabled = !controller.node.items[itemIndex].isEnabled
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
},
|
||||||
|
itemReordered: { [weak self, weak controller] fromId, toId in
|
||||||
|
guard let self, let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let fromIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == fromId }), let toIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == toId }), toIndex < controller.node.items.count else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let fromItem = controller.node.items[fromIndex]
|
||||||
|
let toItem = controller.node.items[toIndex]
|
||||||
|
controller.node.items[fromIndex] = toItem
|
||||||
|
controller.node.items[toIndex] = fromItem
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
|
||||||
|
controller.hapticFeedback.tap()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
if let selectionPanelView = selectionPanel.view as? SelectionPanelComponent.View {
|
||||||
|
if selectionPanelView.superview == nil {
|
||||||
|
self.insertSubview(selectionPanelView, belowSubview: selectionButtonView)
|
||||||
|
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||||
|
selectionPanelView.animateIn(from: buttonView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectionPanelView.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
|
}
|
||||||
|
} else if let selectionPanel = self.selectionPanel {
|
||||||
|
self.selectionPanel = nil
|
||||||
|
if let selectionPanelView = selectionPanel.view as? SelectionPanelComponent.View {
|
||||||
|
if !transition.animation.isImmediate, let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||||
|
selectionPanelView.animateOut(to: buttonView, completion: { [weak selectionPanelView] in
|
||||||
|
selectionPanelView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
selectionPanelView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .stickerEditor = controller.mode {
|
if case .stickerEditor = controller.mode {
|
||||||
@ -2821,6 +2836,8 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
|
|
||||||
let storyDimensions = CGSize(width: 1080.0, height: 1920.0)
|
let storyDimensions = CGSize(width: 1080.0, height: 1920.0)
|
||||||
let storyMaxVideoDuration: Double = 60.0
|
let storyMaxVideoDuration: Double = 60.0
|
||||||
|
let storyMaxCombinedVideoCount: Int = 3
|
||||||
|
let storyMaxCombinedVideoDuration: Double = storyMaxVideoDuration * Double(storyMaxCombinedVideoCount)
|
||||||
|
|
||||||
public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UIDropInteractionDelegate {
|
public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UIDropInteractionDelegate {
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
@ -3489,6 +3506,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
values: initialValues,
|
values: initialValues,
|
||||||
hasHistogram: true
|
hasHistogram: true
|
||||||
)
|
)
|
||||||
|
mediaEditor.maxDuration = storyMaxCombinedVideoDuration
|
||||||
if case .avatarEditor = controller.mode {
|
if case .avatarEditor = controller.mode {
|
||||||
mediaEditor.setVideoIsMuted(true)
|
mediaEditor.setVideoIsMuted(true)
|
||||||
} else if case let .coverEditor(dimensions) = controller.mode {
|
} else if case let .coverEditor(dimensions) = controller.mode {
|
||||||
@ -5075,7 +5093,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
var audioTrimRange: Range<Double>?
|
var audioTrimRange: Range<Double>?
|
||||||
var audioOffset: Double?
|
var audioOffset: Double?
|
||||||
|
|
||||||
if let videoDuration = mediaEditor.originalDuration {
|
if let videoDuration = mediaEditor.originalCappedDuration {
|
||||||
if let videoStart = mediaEditor.values.videoTrimRange?.lowerBound {
|
if let videoStart = mediaEditor.values.videoTrimRange?.lowerBound {
|
||||||
audioOffset = -videoStart
|
audioOffset = -videoStart
|
||||||
} else if let _ = mediaEditor.values.additionalVideoPath, let videoStart = mediaEditor.values.additionalVideoTrimRange?.lowerBound {
|
} else if let _ = mediaEditor.values.additionalVideoPath, let videoStart = mediaEditor.values.additionalVideoTrimRange?.lowerBound {
|
||||||
@ -6694,7 +6712,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
self.postingAvailabilityDisposable = (self.postingAvailabilityPromise.get()
|
self.postingAvailabilityDisposable = (self.postingAvailabilityPromise.get()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] availability in
|
|> deliverOnMainQueue).start(next: { [weak self] availability in
|
||||||
guard let self, availability != .available else {
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case .available = availability {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7341,36 +7362,21 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func completeWithMultipleResults(results: [MediaEditorScreenImpl.Result]) {
|
private func processMultipleItems(items: [EditingItem]) {
|
||||||
// Send all results to completion handler
|
guard !items.isEmpty else {
|
||||||
self.completion(results, { [weak self] finished in
|
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
|
||||||
self?.dismiss()
|
|
||||||
Queue.mainQueue().justDispatch {
|
|
||||||
finished()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private func processMultipleItems() {
|
|
||||||
guard !self.node.items.isEmpty else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let mediaEditor = self.node.mediaEditor, case let .asset(asset) = self.node.subject, let currentItemIndex = self.node.items.firstIndex(where: { $0.asset.localIdentifier == asset.localIdentifier }) {
|
var items = items
|
||||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
if let mediaEditor = self.node.mediaEditor, case let .asset(asset) = self.node.subject, let currentItemIndex = items.firstIndex(where: { $0.asset.localIdentifier == asset.localIdentifier }) {
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
var updatedCurrentItem = items[currentItemIndex]
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
|
||||||
|
|
||||||
var updatedCurrentItem = self.node.items[currentItemIndex]
|
|
||||||
updatedCurrentItem.caption = self.node.getCaption()
|
updatedCurrentItem.caption = self.node.getCaption()
|
||||||
updatedCurrentItem.values = mediaEditor.values
|
updatedCurrentItem.values = mediaEditor.values
|
||||||
self.node.items[currentItemIndex] = updatedCurrentItem
|
items[currentItemIndex] = updatedCurrentItem
|
||||||
}
|
}
|
||||||
|
|
||||||
let multipleResults = Atomic<[MediaEditorScreenImpl.Result]>(value: [])
|
let multipleResults = Atomic<[MediaEditorScreenImpl.Result]>(value: [])
|
||||||
let totalItems = self.node.items.count
|
let totalItems = items.count
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
|
|
||||||
@ -7387,7 +7393,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
|
|
||||||
var order: [Int64] = []
|
var order: [Int64] = []
|
||||||
for (index, item) in self.node.items.enumerated() {
|
for (index, item) in items.enumerated() {
|
||||||
guard item.isEnabled else {
|
guard item.isEnabled else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -7431,7 +7437,14 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
orderedResults.append(item)
|
orderedResults.append(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.completeWithMultipleResults(results: orderedResults)
|
self.completion(results, { [weak self] finished in
|
||||||
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
|
self?.dismiss()
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
finished()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7452,13 +7465,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if let mediaArea = entity.mediaArea {
|
if let mediaArea = entity.mediaArea {
|
||||||
mediaAreas.append(mediaArea)
|
mediaAreas.append(mediaArea)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract stickers from entities
|
|
||||||
extractStickersFromEntity(entity, into: &stickers)
|
extractStickersFromEntity(entity, into: &stickers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process video
|
|
||||||
let firstFrameTime: CMTime
|
let firstFrameTime: CMTime
|
||||||
if let coverImageTimestamp = item.values?.coverImageTimestamp {
|
if let coverImageTimestamp = item.values?.coverImageTimestamp {
|
||||||
firstFrameTime = CMTime(seconds: coverImageTimestamp, preferredTimescale: CMTimeScale(60))
|
firstFrameTime = CMTime(seconds: coverImageTimestamp, preferredTimescale: CMTimeScale(60))
|
||||||
@ -7476,7 +7486,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate duration
|
|
||||||
let duration: Double
|
let duration: Double
|
||||||
if let videoTrimRange = item.values?.videoTrimRange {
|
if let videoTrimRange = item.values?.videoTrimRange {
|
||||||
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
|
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
|
||||||
@ -7484,7 +7493,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
duration = min(asset.duration, storyMaxVideoDuration)
|
duration = min(asset.duration, storyMaxVideoDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate thumbnail frame
|
|
||||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)]) { [weak self] _, cgImage, _, _, _ in
|
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)]) { [weak self] _, cgImage, _, _, _ in
|
||||||
@ -7541,14 +7549,11 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
private func processImageItem(item: EditingItem, index: Int, randomId: Int64, completion: @escaping (MediaEditorScreenImpl.Result) -> Void) {
|
private func processImageItem(item: EditingItem, index: Int, randomId: Int64, completion: @escaping (MediaEditorScreenImpl.Result) -> Void) {
|
||||||
let asset = item.asset
|
let asset = item.asset
|
||||||
|
|
||||||
// Setup temporary media editor for this item
|
|
||||||
let itemMediaEditor = setupMediaEditorForItem(item: item)
|
let itemMediaEditor = setupMediaEditorForItem(item: item)
|
||||||
|
|
||||||
// Get caption for this item
|
|
||||||
var caption = item.caption
|
var caption = item.caption
|
||||||
caption = convertMarkdownToAttributes(caption)
|
caption = convertMarkdownToAttributes(caption)
|
||||||
|
|
||||||
// Media areas and stickers
|
|
||||||
var mediaAreas: [MediaArea] = []
|
var mediaAreas: [MediaArea] = []
|
||||||
var stickers: [TelegramMediaFile] = []
|
var stickers: [TelegramMediaFile] = []
|
||||||
|
|
||||||
@ -7557,13 +7562,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if let mediaArea = entity.mediaArea {
|
if let mediaArea = entity.mediaArea {
|
||||||
mediaAreas.append(mediaArea)
|
mediaAreas.append(mediaArea)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract stickers from entities
|
|
||||||
extractStickersFromEntity(entity, into: &stickers)
|
extractStickersFromEntity(entity, into: &stickers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request full-size image
|
|
||||||
let options = PHImageRequestOptions()
|
let options = PHImageRequestOptions()
|
||||||
options.deliveryMode = .highQualityFormat
|
options.deliveryMode = .highQualityFormat
|
||||||
options.isNetworkAccessAllowed = true
|
options.isNetworkAccessAllowed = true
|
||||||
@ -7664,10 +7666,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject, let actualSubject = self.node.actualSubject else {
|
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject, let actualSubject = self.node.actualSubject else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
|
||||||
|
|
||||||
var caption = self.node.getCaption()
|
var caption = self.node.getCaption()
|
||||||
caption = convertMarkdownToAttributes(caption)
|
caption = convertMarkdownToAttributes(caption)
|
||||||
@ -7680,6 +7678,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
randomId = Int64.random(in: .min ... .max)
|
randomId = Int64.random(in: .min ... .max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let codableEntities = mediaEditor.values.entities
|
||||||
var mediaAreas: [MediaArea] = []
|
var mediaAreas: [MediaArea] = []
|
||||||
if case let .draft(draft, _) = actualSubject {
|
if case let .draft(draft, _) = actualSubject {
|
||||||
if draft.values.entities != codableEntities {
|
if draft.values.entities != codableEntities {
|
||||||
@ -8108,6 +8107,15 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateMediaEditorEntities() {
|
||||||
|
guard let mediaEditor = self.node.mediaEditor else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||||
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||||
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
}
|
||||||
|
|
||||||
private var didComplete = false
|
private var didComplete = false
|
||||||
func requestStoryCompletion(animated: Bool) {
|
func requestStoryCompletion(animated: Bool) {
|
||||||
@ -8117,7 +8125,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
self.didComplete = true
|
self.didComplete = true
|
||||||
|
|
||||||
self.dismissAllTooltips()
|
self.updateMediaEditorEntities()
|
||||||
|
|
||||||
mediaEditor.stop()
|
mediaEditor.stop()
|
||||||
mediaEditor.invalidate()
|
mediaEditor.invalidate()
|
||||||
@ -8127,11 +8135,42 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.node.items.count(where: { $0.isEnabled }) > 1 {
|
var multipleItems: [EditingItem] = []
|
||||||
self.processMultipleItems()
|
if self.node.items.count > 1 {
|
||||||
|
multipleItems = self.node.items.filter({ $0.isEnabled })
|
||||||
|
} else if case let .asset(asset) = self.node.subject {
|
||||||
|
let duration: Double
|
||||||
|
if let playerDuration = mediaEditor.duration {
|
||||||
|
duration = playerDuration
|
||||||
|
} else {
|
||||||
|
duration = asset.duration
|
||||||
|
}
|
||||||
|
if duration > storyMaxVideoDuration {
|
||||||
|
let originalDuration = mediaEditor.originalDuration ?? asset.duration
|
||||||
|
let values = mediaEditor.values
|
||||||
|
|
||||||
|
let storyCount = min(storyMaxCombinedVideoCount, Int(ceil(duration / storyMaxVideoDuration)))
|
||||||
|
var start = values.videoTrimRange?.lowerBound ?? 0
|
||||||
|
for _ in 0 ..< storyCount {
|
||||||
|
let trimmedValues = values.withUpdatedVideoTrimRange(start ..< min(start + storyMaxVideoDuration, originalDuration))
|
||||||
|
|
||||||
|
var editingItem = EditingItem(asset: asset)
|
||||||
|
editingItem.caption = self.node.getCaption()
|
||||||
|
editingItem.values = trimmedValues
|
||||||
|
multipleItems.append(editingItem)
|
||||||
|
|
||||||
|
start += storyMaxVideoDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if multipleItems.count > 1 {
|
||||||
|
self.processMultipleItems(items: multipleItems)
|
||||||
} else {
|
} else {
|
||||||
self.processSingleItem()
|
self.processSingleItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.dismissAllTooltips()
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestStickerCompletion(animated: Bool) {
|
func requestStickerCompletion(animated: Bool) {
|
||||||
@ -8157,10 +8196,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
self.updateMediaEditorEntities()
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
|
||||||
|
|
||||||
if let image = mediaEditor.resultImage {
|
if let image = mediaEditor.resultImage {
|
||||||
let values = mediaEditor.values.withUpdatedQualityPreset(.sticker)
|
let values = mediaEditor.values.withUpdatedQualityPreset(.sticker)
|
||||||
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: CGSize(width: 512, height: 512), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
|
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: CGSize(width: 512, height: 512), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
|
||||||
@ -8181,11 +8218,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if let navigationController = self.navigationController as? NavigationController {
|
if let navigationController = self.navigationController as? NavigationController {
|
||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
self.updateMediaEditorEntities()
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
|
||||||
|
|
||||||
if let image = mediaEditor.resultImage {
|
if let image = mediaEditor.resultImage {
|
||||||
let values = mediaEditor.values.withUpdatedCoverDimensions(dimensions)
|
let values = mediaEditor.values.withUpdatedCoverDimensions(dimensions)
|
||||||
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: dimensions.aspectFitted(CGSize(width: 1080, height: 1080)), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
|
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: dimensions.aspectFitted(CGSize(width: 1080, height: 1080)), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
|
||||||
@ -8786,12 +8821,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = self.context
|
self.updateMediaEditorEntities()
|
||||||
|
|
||||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
|
||||||
|
|
||||||
let isSticker = toStickerResource != nil
|
let isSticker = toStickerResource != nil
|
||||||
if !isSticker {
|
if !isSticker {
|
||||||
self.previousSavedValues = mediaEditor.values
|
self.previousSavedValues = mediaEditor.values
|
||||||
@ -8820,6 +8851,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let context = self.context
|
||||||
if mediaEditor.resultIsVideo {
|
if mediaEditor.resultIsVideo {
|
||||||
if !isSticker {
|
if !isSticker {
|
||||||
mediaEditor.maybePauseVideo()
|
mediaEditor.maybePauseVideo()
|
||||||
|
|||||||
@ -84,6 +84,7 @@ public final class MediaScrubberComponent: Component {
|
|||||||
let position: Double
|
let position: Double
|
||||||
let minDuration: Double
|
let minDuration: Double
|
||||||
let maxDuration: Double
|
let maxDuration: Double
|
||||||
|
let segmentDuration: Double?
|
||||||
let isPlaying: Bool
|
let isPlaying: Bool
|
||||||
|
|
||||||
let tracks: [Track]
|
let tracks: [Track]
|
||||||
@ -112,6 +113,7 @@ public final class MediaScrubberComponent: Component {
|
|||||||
position: Double,
|
position: Double,
|
||||||
minDuration: Double,
|
minDuration: Double,
|
||||||
maxDuration: Double,
|
maxDuration: Double,
|
||||||
|
segmentDuration: Double? = nil,
|
||||||
isPlaying: Bool,
|
isPlaying: Bool,
|
||||||
tracks: [Track],
|
tracks: [Track],
|
||||||
isCollage: Bool,
|
isCollage: Bool,
|
||||||
@ -135,6 +137,7 @@ public final class MediaScrubberComponent: Component {
|
|||||||
self.position = position
|
self.position = position
|
||||||
self.minDuration = minDuration
|
self.minDuration = minDuration
|
||||||
self.maxDuration = maxDuration
|
self.maxDuration = maxDuration
|
||||||
|
self.segmentDuration = segmentDuration
|
||||||
self.isPlaying = isPlaying
|
self.isPlaying = isPlaying
|
||||||
self.tracks = tracks
|
self.tracks = tracks
|
||||||
self.isCollage = isCollage
|
self.isCollage = isCollage
|
||||||
@ -171,6 +174,9 @@ public final class MediaScrubberComponent: Component {
|
|||||||
if lhs.maxDuration != rhs.maxDuration {
|
if lhs.maxDuration != rhs.maxDuration {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.segmentDuration != rhs.segmentDuration {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.isPlaying != rhs.isPlaying {
|
if lhs.isPlaying != rhs.isPlaying {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -624,6 +630,7 @@ public final class MediaScrubberComponent: Component {
|
|||||||
isSelected: isSelected,
|
isSelected: isSelected,
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
duration: self.duration,
|
duration: self.duration,
|
||||||
|
segmentDuration: lowestVideoId == track.id ? component.segmentDuration : nil,
|
||||||
transition: trackTransition
|
transition: trackTransition
|
||||||
)
|
)
|
||||||
trackLayout[id] = (CGRect(origin: CGPoint(x: 0.0, y: totalHeight), size: trackSize), trackTransition, animateTrackIn)
|
trackLayout[id] = (CGRect(origin: CGPoint(x: 0.0, y: totalHeight), size: trackSize), trackTransition, animateTrackIn)
|
||||||
@ -675,6 +682,7 @@ public final class MediaScrubberComponent: Component {
|
|||||||
isSelected: false,
|
isSelected: false,
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
duration: self.duration,
|
duration: self.duration,
|
||||||
|
segmentDuration: nil,
|
||||||
transition: trackTransition
|
transition: trackTransition
|
||||||
)
|
)
|
||||||
trackTransition.setFrame(view: trackView, frame: CGRect(origin: .zero, size: trackSize))
|
trackTransition.setFrame(view: trackView, frame: CGRect(origin: .zero, size: trackSize))
|
||||||
@ -955,6 +963,9 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
fileprivate let audioContentMaskView: UIImageView
|
fileprivate let audioContentMaskView: UIImageView
|
||||||
fileprivate let audioIconView: UIImageView
|
fileprivate let audioIconView: UIImageView
|
||||||
fileprivate let audioTitle = ComponentView<Empty>()
|
fileprivate let audioTitle = ComponentView<Empty>()
|
||||||
|
|
||||||
|
fileprivate var segmentTitles: [Int32: ComponentView<Empty>] = [:]
|
||||||
|
fileprivate var segmentLayers: [Int32: SimpleLayer] = [:]
|
||||||
|
|
||||||
fileprivate let videoTransparentFramesContainer = UIView()
|
fileprivate let videoTransparentFramesContainer = UIView()
|
||||||
fileprivate var videoTransparentFrameLayers: [VideoFrameLayer] = []
|
fileprivate var videoTransparentFrameLayers: [VideoFrameLayer] = []
|
||||||
@ -1142,6 +1153,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
isSelected: Bool,
|
isSelected: Bool,
|
||||||
availableSize: CGSize,
|
availableSize: CGSize,
|
||||||
duration: Double,
|
duration: Double,
|
||||||
|
segmentDuration: Double?,
|
||||||
transition: ComponentTransition
|
transition: ComponentTransition
|
||||||
) -> CGSize {
|
) -> CGSize {
|
||||||
let previousParams = self.params
|
let previousParams = self.params
|
||||||
@ -1477,6 +1489,86 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
|
|||||||
transition.setFrame(view: self.vibrancyView, frame: CGRect(origin: .zero, size: containerFrame.size))
|
transition.setFrame(view: self.vibrancyView, frame: CGRect(origin: .zero, size: containerFrame.size))
|
||||||
transition.setFrame(view: self.vibrancyContainer, frame: CGRect(origin: .zero, size: containerFrame.size))
|
transition.setFrame(view: self.vibrancyContainer, frame: CGRect(origin: .zero, size: containerFrame.size))
|
||||||
|
|
||||||
|
var segmentCount = 0
|
||||||
|
var segmentOrigin: CGFloat = 0.0
|
||||||
|
var segmentWidth: CGFloat = 0.0
|
||||||
|
if let segmentDuration {
|
||||||
|
if duration > segmentDuration {
|
||||||
|
let fraction = segmentDuration / duration
|
||||||
|
segmentCount = Int(ceil(duration / segmentDuration)) - 1
|
||||||
|
segmentWidth = floorToScreenPixels(containerFrame.width * fraction)
|
||||||
|
}
|
||||||
|
if let trimRange = track.trimRange {
|
||||||
|
if trimRange.lowerBound > 0.0 {
|
||||||
|
let fraction = trimRange.lowerBound / duration
|
||||||
|
segmentOrigin = floorToScreenPixels(containerFrame.width * fraction)
|
||||||
|
}
|
||||||
|
let actualSegmentCount = Int(ceil((trimRange.upperBound - trimRange.lowerBound) / segmentDuration)) - 1
|
||||||
|
segmentCount = min(actualSegmentCount, segmentCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validIds = Set<Int32>()
|
||||||
|
var segmentFrame = CGRect(x: segmentOrigin + segmentWidth, y: 0.0, width: 1.0, height: containerFrame.size.height)
|
||||||
|
for i in 0 ..< segmentCount {
|
||||||
|
let id = Int32(i)
|
||||||
|
validIds.insert(id)
|
||||||
|
|
||||||
|
let segmentLayer: SimpleLayer
|
||||||
|
let segmentTitle: ComponentView<Empty>
|
||||||
|
|
||||||
|
var segmentTransition = transition
|
||||||
|
if let currentLayer = self.segmentLayers[id], let currentTitle = self.segmentTitles[id] {
|
||||||
|
segmentLayer = currentLayer
|
||||||
|
segmentTitle = currentTitle
|
||||||
|
} else {
|
||||||
|
segmentTransition = .immediate
|
||||||
|
segmentLayer = SimpleLayer()
|
||||||
|
segmentLayer.backgroundColor = UIColor.white.cgColor
|
||||||
|
segmentTitle = ComponentView<Empty>()
|
||||||
|
|
||||||
|
self.segmentLayers[id] = segmentLayer
|
||||||
|
self.segmentTitles[id] = segmentTitle
|
||||||
|
|
||||||
|
self.containerView.layer.addSublayer(segmentLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(layer: segmentLayer, frame: segmentFrame)
|
||||||
|
|
||||||
|
let segmentTitleSize = segmentTitle.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "#\(i + 2)", font: Font.semibold(11.0), textColor: .white)),
|
||||||
|
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.4),
|
||||||
|
textShadowBlur: 1.0
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: containerFrame.size
|
||||||
|
)
|
||||||
|
if let view = segmentTitle.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.containerView.addSubview(view)
|
||||||
|
}
|
||||||
|
segmentTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: segmentFrame.maxX + 2.0, y: 2.0), size: segmentTitleSize))
|
||||||
|
}
|
||||||
|
segmentFrame.origin.x += segmentWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeIds: [Int32] = []
|
||||||
|
for (id, segmentLayer) in self.segmentLayers {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
segmentLayer.removeFromSuperlayer()
|
||||||
|
if let segmentTitle = self.segmentTitles[id] {
|
||||||
|
segmentTitle.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.segmentLayers.removeValue(forKey: id)
|
||||||
|
self.segmentTitles.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
return scrubberSize
|
return scrubberSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -613,6 +613,8 @@ private final class PeerInfoInteraction {
|
|||||||
let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||||
let editingOpenAffiliateProgram: () -> Void
|
let editingOpenAffiliateProgram: () -> Void
|
||||||
let editingOpenVerifyAccounts: () -> Void
|
let editingOpenVerifyAccounts: () -> Void
|
||||||
|
let editingToggleAutoTranslate: (Bool) -> Void
|
||||||
|
let displayAutoTranslateLocked: () -> Void
|
||||||
let getController: () -> ViewController?
|
let getController: () -> ViewController?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -683,6 +685,8 @@ private final class PeerInfoInteraction {
|
|||||||
openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||||
editingOpenAffiliateProgram: @escaping () -> Void,
|
editingOpenAffiliateProgram: @escaping () -> Void,
|
||||||
editingOpenVerifyAccounts: @escaping () -> Void,
|
editingOpenVerifyAccounts: @escaping () -> Void,
|
||||||
|
editingToggleAutoTranslate: @escaping (Bool) -> Void,
|
||||||
|
displayAutoTranslateLocked: @escaping () -> Void,
|
||||||
getController: @escaping () -> ViewController?
|
getController: @escaping () -> ViewController?
|
||||||
) {
|
) {
|
||||||
self.openUsername = openUsername
|
self.openUsername = openUsername
|
||||||
@ -752,6 +756,8 @@ private final class PeerInfoInteraction {
|
|||||||
self.openBirthdayContextMenu = openBirthdayContextMenu
|
self.openBirthdayContextMenu = openBirthdayContextMenu
|
||||||
self.editingOpenAffiliateProgram = editingOpenAffiliateProgram
|
self.editingOpenAffiliateProgram = editingOpenAffiliateProgram
|
||||||
self.editingOpenVerifyAccounts = editingOpenVerifyAccounts
|
self.editingOpenVerifyAccounts = editingOpenVerifyAccounts
|
||||||
|
self.editingToggleAutoTranslate = editingToggleAutoTranslate
|
||||||
|
self.displayAutoTranslateLocked = displayAutoTranslateLocked
|
||||||
self.getController = getController
|
self.getController = getController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2154,6 +2160,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
let ItemBanned = 11
|
let ItemBanned = 11
|
||||||
let ItemRecentActions = 12
|
let ItemRecentActions = 12
|
||||||
let ItemAffiliatePrograms = 13
|
let ItemAffiliatePrograms = 13
|
||||||
|
let ItemPeerAutoTranslate = 14
|
||||||
|
|
||||||
let isCreator = channel.flags.contains(.isCreator)
|
let isCreator = channel.flags.contains(.isCreator)
|
||||||
|
|
||||||
@ -2268,6 +2275,18 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
|
||||||
interaction.editingOpenNameColorSetup()
|
interaction.editingOpenNameColorSetup()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
var isLocked = true
|
||||||
|
if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel >= 3 {
|
||||||
|
isLocked = false
|
||||||
|
}
|
||||||
|
items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemPeerAutoTranslate, text: presentationData.strings.Channel_Info_AutoTranslate, value: false, icon: UIImage(bundleImageName: "Settings/Menu/AutoTranslate"), isLocked: isLocked, toggled: { value in
|
||||||
|
if isLocked {
|
||||||
|
interaction.displayAutoTranslateLocked()
|
||||||
|
} else {
|
||||||
|
interaction.editingToggleAutoTranslate(value)
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
var canEditMembers = false
|
var canEditMembers = false
|
||||||
@ -3194,6 +3213,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.editingOpenVerifyAccounts()
|
self.editingOpenVerifyAccounts()
|
||||||
|
}, editingToggleAutoTranslate: { [weak self] isEnabled in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.toggleAutoTranslate(isEnabled: isEnabled)
|
||||||
|
}, displayAutoTranslateLocked: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.displayAutoTranslateLocked()
|
||||||
},
|
},
|
||||||
getController: { [weak self] in
|
getController: { [weak self] in
|
||||||
return self?.controller
|
return self?.controller
|
||||||
@ -9127,6 +9156,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func toggleAutoTranslate(isEnabled: Bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func displayAutoTranslateLocked() {
|
||||||
|
let _ = combineLatest(
|
||||||
|
queue: Queue.mainQueue(),
|
||||||
|
context.engine.peers.getChannelBoostStatus(peerId: self.peerId),
|
||||||
|
context.engine.peers.getMyBoostStatus()
|
||||||
|
).startStandalone(next: { [weak self] boostStatus, myBoostStatus in
|
||||||
|
guard let self, let controller = self.controller, let boostStatus, let myBoostStatus else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let boostController = self.context.sharedContext.makePremiumBoostLevelsController(context: self.context, peerId: self.peerId, subject: .autoTranslate, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: false, openStats: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.openStats(section: .boosts, boostStatus: boostStatus)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
controller.push(boostController)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func toggleForumTopics(isEnabled: Bool) {
|
private func toggleForumTopics(isEnabled: Bool) {
|
||||||
guard let data = self.data, let peer = data.peer else {
|
guard let data = self.data, let peer = data.peer else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -608,9 +608,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
},
|
},
|
||||||
updateResellStars: { [weak self] price in
|
updateResellStars: { [weak self] price in
|
||||||
guard let self, let reference = product.reference else {
|
guard let self, let reference = product.reference else {
|
||||||
return
|
return .never()
|
||||||
}
|
}
|
||||||
self.profileGifts.updateStarGiftResellPrice(reference: reference, price: price)
|
return self.profileGifts.updateStarGiftResellPrice(reference: reference, price: price)
|
||||||
},
|
},
|
||||||
togglePinnedToTop: { [weak self] pinnedToTop in
|
togglePinnedToTop: { [weak self] pinnedToTop in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
|||||||
@ -173,6 +173,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
|
|
||||||
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
|
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
|
||||||
|
|
||||||
|
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
let controller = environment.controller
|
let controller = environment.controller
|
||||||
@ -366,8 +368,14 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
case let .transaction(transaction, parentPeer):
|
case let .transaction(transaction, parentPeer):
|
||||||
if let starGift = transaction.starGift {
|
if let starGift = transaction.starGift {
|
||||||
titleText = strings.Stars_Transaction_Gift_Title
|
switch starGift {
|
||||||
descriptionText = ""
|
case .generic:
|
||||||
|
titleText = strings.Stars_Transaction_Gift_Title
|
||||||
|
descriptionText = ""
|
||||||
|
case let .unique(gift):
|
||||||
|
titleText = gift.title
|
||||||
|
descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))"
|
||||||
|
}
|
||||||
count = transaction.count
|
count = transaction.count
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
@ -665,14 +673,23 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
amountText = "+ \(formattedAmount)"
|
amountText = "+ \(formattedAmount)"
|
||||||
countColor = theme.list.itemDisclosureActions.constructive.fillColor
|
if case .unique = giftAnimationSubject {
|
||||||
|
countColor = .white
|
||||||
|
} else {
|
||||||
|
countColor = theme.list.itemDisclosureActions.constructive.fillColor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var titleFont = Font.bold(25.0)
|
||||||
|
if case .unique = giftAnimationSubject {
|
||||||
|
titleFont = Font.bold(20.0)
|
||||||
|
}
|
||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: titleText,
|
string: titleText,
|
||||||
font: Font.bold(25.0),
|
font: titleFont,
|
||||||
textColor: headerTextColor,
|
textColor: headerTextColor,
|
||||||
paragraphAlignment: .center
|
paragraphAlignment: .center
|
||||||
)),
|
)),
|
||||||
@ -723,7 +740,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
if let giftAnimationSubject {
|
if let giftAnimationSubject {
|
||||||
let animationHeight: CGFloat
|
let animationHeight: CGFloat
|
||||||
if case .unique = giftAnimationSubject {
|
if case .unique = giftAnimationSubject {
|
||||||
animationHeight = 240.0
|
animationHeight = 268.0
|
||||||
} else {
|
} else {
|
||||||
animationHeight = 210.0
|
animationHeight = 210.0
|
||||||
}
|
}
|
||||||
@ -731,7 +748,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
component: GiftCompositionComponent(
|
component: GiftCompositionComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
subject: giftAnimationSubject
|
subject: giftAnimationSubject,
|
||||||
|
externalState: giftCompositionExternalState
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: context.availableSize.width, height: animationHeight),
|
availableSize: CGSize(width: context.availableSize.width, height: animationHeight),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
@ -816,6 +834,14 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_GiftUpgrade, font: tableFont, textColor: tableTextColor)))
|
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_GiftUpgrade, font: tableFont, textColor: tableTextColor)))
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
} else if case .unique = giftAnimationSubject {
|
||||||
|
tableItems.append(.init(
|
||||||
|
id: "reason",
|
||||||
|
title: strings.Stars_Transaction_Giveaway_Reason,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(text: .plain(NSAttributedString(string: count < StarsAmount.zero ? strings.Stars_Transaction_GiftPurchase : strings.Stars_Transaction_GiftSale, font: tableFont, textColor: tableTextColor)))
|
||||||
|
)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGift, toPeer == nil {
|
if isGift, toPeer == nil {
|
||||||
@ -1300,13 +1326,29 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var originY: CGFloat = 156.0
|
var originY: CGFloat = 156.0
|
||||||
if let _ = giftAnimationSubject {
|
switch giftAnimationSubject {
|
||||||
originY += 18.0
|
case .generic:
|
||||||
|
originY += 20.0
|
||||||
|
case .unique:
|
||||||
|
originY += 34.0
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
context.add(title
|
context.add(title
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY))
|
||||||
)
|
)
|
||||||
originY += 21.0
|
if case .unique = giftAnimationSubject {
|
||||||
|
originY += 17.0
|
||||||
|
} else {
|
||||||
|
originY += 21.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let vibrantColor: UIColor
|
||||||
|
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||||
|
vibrantColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
|
||||||
|
} else {
|
||||||
|
vibrantColor = UIColor.white.withAlphaComponent(0.6)
|
||||||
|
}
|
||||||
|
|
||||||
var descriptionSize: CGSize = .zero
|
var descriptionSize: CGSize = .zero
|
||||||
if !descriptionText.isEmpty {
|
if !descriptionText.isEmpty {
|
||||||
@ -1316,8 +1358,18 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var textFont = Font.regular(15.0)
|
||||||
|
let boldTextFont = Font.semibold(15.0)
|
||||||
|
var textColor = theme.actionSheet.secondaryTextColor
|
||||||
|
if case .unique = giftAnimationSubject {
|
||||||
|
textFont = Font.regular(13.0)
|
||||||
|
textColor = vibrantColor
|
||||||
|
} else if countOnTop && !isSubscriber {
|
||||||
|
textColor = theme.list.itemPrimaryTextColor
|
||||||
|
}
|
||||||
|
let linkColor = theme.actionSheet.controlAccentColor
|
||||||
|
|
||||||
let textColor = countOnTop && !isSubscriber ? theme.list.itemPrimaryTextColor : textColor
|
|
||||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||||
return (TelegramTextAttributes.URL, contents)
|
return (TelegramTextAttributes.URL, contents)
|
||||||
})
|
})
|
||||||
@ -1362,7 +1414,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
context.add(description
|
context.add(description
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: descriptionOrigin + description.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: descriptionOrigin + description.size.height / 2.0))
|
||||||
)
|
)
|
||||||
originY += description.size.height + 10.0
|
originY += description.size.height
|
||||||
|
|
||||||
|
if case .unique = giftAnimationSubject {
|
||||||
|
originY += 6.0
|
||||||
|
} else {
|
||||||
|
originY += 10.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let amountSpacing: CGFloat = countBackgroundColor != nil ? 4.0 : 1.0
|
let amountSpacing: CGFloat = countBackgroundColor != nil ? 4.0 : 1.0
|
||||||
|
|||||||
@ -317,9 +317,18 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
uniqueGift = gift
|
uniqueGift = gift
|
||||||
} else {
|
} else {
|
||||||
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||||
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift
|
switch starGift {
|
||||||
if case let .generic(gift) = starGift {
|
case let .generic(gift):
|
||||||
itemFile = gift.file
|
itemFile = gift.file
|
||||||
|
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift
|
||||||
|
case let .unique(gift):
|
||||||
|
for attribute in gift.attributes {
|
||||||
|
if case let .model(_, file, _) = attribute {
|
||||||
|
itemFile = file
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_GiftSale : environment.strings.Stars_Intro_Transaction_GiftPurchase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let _ = item.giveawayMessageId {
|
} else if let _ = item.giveawayMessageId {
|
||||||
|
|||||||
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/AutoTranslate.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/AutoTranslate.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "translation.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Menu/AutoTranslate.imageset/translation.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Menu/AutoTranslate.imageset/translation.pdf
vendored
Normal file
Binary file not shown.
@ -977,8 +977,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
var setPresentationCall: ((PresentationCall?) -> Void)?
|
var setPresentationCall: ((PresentationCall?) -> Void)?
|
||||||
let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), firebaseSecretStream: self.firebaseSecretStream.get(), setNotificationCall: { call in
|
let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), firebaseSecretStream: self.firebaseSecretStream.get(), setNotificationCall: { call in
|
||||||
setPresentationCall?(call)
|
setPresentationCall?(call)
|
||||||
}, navigateToChat: { accountId, peerId, messageId in
|
}, navigateToChat: { accountId, peerId, messageId, alwaysKeepMessageId in
|
||||||
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId, storyId: nil)
|
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId, storyId: nil, alwaysKeepMessageId: alwaysKeepMessageId)
|
||||||
}, displayUpgradeProgress: { progress in
|
}, displayUpgradeProgress: { progress in
|
||||||
if let progress = progress {
|
if let progress = progress {
|
||||||
if self.dataImportSplash == nil {
|
if self.dataImportSplash == nil {
|
||||||
@ -2736,7 +2736,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openChatWhenReady(accountId: AccountRecordId?, peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false, storyId: StoryId?, openAppIfAny: Bool = false) {
|
private func openChatWhenReady(accountId: AccountRecordId?, peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false, storyId: StoryId?, openAppIfAny: Bool = false, alwaysKeepMessageId: Bool = false) {
|
||||||
let signal = self.sharedContextPromise.get()
|
let signal = self.sharedContextPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
@ -2755,7 +2755,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
}
|
}
|
||||||
self.openChatWhenReadyDisposable.set((signal
|
self.openChatWhenReadyDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { context in
|
|> deliverOnMainQueue).start(next: { context in
|
||||||
context.openChatWithPeerId(peerId: peerId, threadId: threadId, messageId: messageId, activateInput: activateInput, storyId: storyId, openAppIfAny: openAppIfAny)
|
context.openChatWithPeerId(peerId: peerId, threadId: threadId, messageId: messageId, activateInput: activateInput, storyId: storyId, openAppIfAny: openAppIfAny, alwaysKeepMessageId: alwaysKeepMessageId)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -896,7 +896,7 @@ final class AuthorizedApplicationContext {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func openChatWithPeerId(peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false, storyId: StoryId?, openAppIfAny: Bool = false) {
|
func openChatWithPeerId(peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false, storyId: StoryId?, openAppIfAny: Bool = false, alwaysKeepMessageId: Bool = false) {
|
||||||
if let storyId {
|
if let storyId {
|
||||||
var controllers = self.rootController.viewControllers
|
var controllers = self.rootController.viewControllers
|
||||||
controllers = controllers.filter { c in
|
controllers = controllers.filter { c in
|
||||||
@ -950,7 +950,7 @@ final class AuthorizedApplicationContext {
|
|||||||
if openAppIfAny, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.rootController.viewControllers.last as? ViewController {
|
if openAppIfAny, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.rootController.viewControllers.last as? ViewController {
|
||||||
self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil)
|
self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil)
|
||||||
} else {
|
} else {
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: alwaysKeepMessageId || isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ public func makeTempContext(
|
|||||||
firebaseSecretStream: .never(),
|
firebaseSecretStream: .never(),
|
||||||
setNotificationCall: { _ in
|
setNotificationCall: { _ in
|
||||||
},
|
},
|
||||||
navigateToChat: { _, _, _ in
|
navigateToChat: { _, _, _, _ in
|
||||||
}, displayUpgradeProgress: { _ in
|
}, displayUpgradeProgress: { _ in
|
||||||
},
|
},
|
||||||
appDelegate: nil
|
appDelegate: nil
|
||||||
|
|||||||
@ -140,7 +140,7 @@ public final class NotificationViewControllerImpl {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), externalRequestVerificationStream: .never(), externalRecaptchaRequestVerification: { _, _ in return .never() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
|
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), externalRequestVerificationStream: .never(), externalRecaptchaRequestVerification: { _, _ in return .never() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _, _ in }, appDelegate: nil)
|
||||||
|
|
||||||
presentationDataPromise.set(sharedAccountContext!.presentationData)
|
presentationDataPromise.set(sharedAccountContext!.presentationData)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,7 +133,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?) -> Void
|
private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?, Bool) -> Void
|
||||||
|
|
||||||
private let apsNotificationToken: Signal<Data?, NoError>
|
private let apsNotificationToken: Signal<Data?, NoError>
|
||||||
private let voipNotificationToken: Signal<Data?, NoError>
|
private let voipNotificationToken: Signal<Data?, NoError>
|
||||||
@ -268,7 +268,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|
|
||||||
private let energyUsageAutomaticDisposable = MetaDisposable()
|
private let energyUsageAutomaticDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager<TelegramAccountManagerTypes>, appLockContext: AppLockContext, notificationController: NotificationContainerController?, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?) {
|
init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager<TelegramAccountManagerTypes>, appLockContext: AppLockContext, notificationController: NotificationContainerController?, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?, Bool) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?) {
|
||||||
assert(Queue.mainQueue().isCurrent())
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
precondition(!testHasInstance)
|
precondition(!testHasInstance)
|
||||||
@ -1760,7 +1760,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) {
|
public func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) {
|
||||||
self.navigateToChatImpl(accountId, peerId, messageId)
|
self.navigateToChatImpl(accountId, peerId, messageId, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tag: HistoryViewInputTag?) -> Signal<(MessageIndex?, Bool), NoError> {
|
public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tag: HistoryViewInputTag?) -> Signal<(MessageIndex?, Bool), NoError> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user