Merge commit '9e8bf48a962ac24a0c7ca00a5f6cb05ebb8aaabd'
@ -329,9 +329,6 @@ alternate_icon_folders = [
|
||||
"Premium",
|
||||
"PremiumBlack",
|
||||
"PremiumTurbo",
|
||||
"PremiumCoffee",
|
||||
"PremiumDuck",
|
||||
"PremiumSteam",
|
||||
]
|
||||
|
||||
[
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 44 KiB |
@ -10105,10 +10105,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"ChatContextMenu.TextSelectionTip2" = "Hold on a word, then move cursor to select more| text to copy or quote.";
|
||||
|
||||
"Appearance.AppIconCoffee" = "Coffee";
|
||||
"Appearance.AppIconDuck" = "Duck";
|
||||
"Appearance.AppIconSteam" = "Steam";
|
||||
|
||||
"Notification.GiftLink" = "You received a gift";
|
||||
|
||||
"MESSAGE_GIFTCODE" = "%1$@ sent you a Gift Code for %2$@ months of Telegram Premium";
|
||||
@ -10123,6 +10119,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Channel.AdminLog.MessageChangedNameColorSet" = "%1$@ set name color to %2$@";
|
||||
"Channel.AdminLog.MessageChangedBackgroundEmojiSet" = "%1$@ set background emoji to %2$@";
|
||||
"Channel.AdminLog.MessageChangedBackgroundEmojiRemoved" = "%1$@ removed background emoji";
|
||||
|
||||
"Appearance.NameColor" = "Your Name Color";
|
||||
|
||||
@ -10197,3 +10194,209 @@ Sorry for the inconvenience.";
|
||||
"Conversation.MessageOptionsEnlargeVideo" = "Enlarge Video";
|
||||
"Conversation.LinkOptionsCancel" = "Do Not Preview";
|
||||
"Conversation.MessageOptionsTabLink" = "Link";
|
||||
|
||||
"Stats.Boosts.ShowMoreBoosts_1" = "Show %@ More Boost";
|
||||
"Stats.Boosts.ShowMoreBoosts_any" = "Show %@ More Boosts";
|
||||
|
||||
"ReassignBoost.Title" = "Reassign Boosts";
|
||||
"ReassignBoost.Description" = "To boost **%1$@**, reassign a previous boost or gift **Telegram Premium** to a friend to get **%2$@** additional boosts.";
|
||||
"ReassignBoost.ReassignBoosts" = "Reassign Boosts";
|
||||
"ReassignBoost.AvailableIn" = "Available in %@";
|
||||
"ReassignBoost.ExpiresOn" = "Boost expires on %@";
|
||||
"ReassignBoost.WaitForCooldown" = "Wait until the boost is available or get **%1$@** more boosts by gifting a **Telegram Premium** subscription.";
|
||||
|
||||
"ReassignBoost.Success" = "%1$@ are reassigned from %2$@.";
|
||||
"ReassignBoost.Boosts_1" = "%@ boost";
|
||||
"ReassignBoost.Boosts_any" = "%@ boosts";
|
||||
"ReassignBoost.OtherChannels_1" = "%@ other channel";
|
||||
"ReassignBoost.OtherChannels_any" = "%@ other channels";
|
||||
|
||||
"ChannelBoost.MoreBoosts.Title" = "More Boosts Needed";
|
||||
"ChannelBoost.MoreBoosts.Text" = "To boost **%1$@** again, gift **Telegram Premium** to a friend and get **%2$@** additional boosts.";
|
||||
|
||||
"BoostGift.Title" = "Boosts via Gifts";
|
||||
"BoostGift.Description" = "Get more boosts for your channel by gifting\nPremium to your subscribers.";
|
||||
"BoostGift.PrepaidGiveawayTitle" = "PREPAID GIVEAWAY";
|
||||
"BoostGift.PrepaidGiveawayCount_1" = "%@ Telegram Premium";
|
||||
"BoostGift.PrepaidGiveawayCount_any" = "%@ Telegram Premium";
|
||||
"BoostGift.PrepaidGiveawayMonths" = "%@-month subscriptions";
|
||||
"BoostGift.CreateGiveaway" = "Create Giveaway";
|
||||
"BoostGift.CreateGiveawayInfo" = "winners are chosen randomly";
|
||||
"BoostGift.AwardSpecificUsers" = "Award Specific Users";
|
||||
"BoostGift.SelectRecipients" = "select recipients";
|
||||
"BoostGift.QuantityTitle" = "QUANTITY OF PRIZES";
|
||||
"BoostGift.QuantityBoosts_1" = "%@ BOOST";
|
||||
"BoostGift.QuantityBoosts_any" = "%@ BOOSTS";
|
||||
"BoostGift.QuantityInfo" = "Choose how many Premium subscriptions to give away and boosts to receive.";
|
||||
"BoostGift.ChannelsTitle" = "CHANNELS INCLUDED IN THE GIVEAWAY";
|
||||
"BoostGift.AddChannel" = "Add Channel";
|
||||
"BoostGift.ChannelsBoosts_1" = "this channel will receive %@ boost";
|
||||
"BoostGift.ChannelsBoosts_any" = "this channel will receive %@ boosts";
|
||||
"BoostGift.ChannelsInfo" = "Choose the channels users need to be subscribed to take part in the giveaway";
|
||||
"BoostGift.UsersTitle" = "USERS ELIGIBLE FOR THE GIVEAWAY";
|
||||
"BoostGift.FromCountries_1" = "from %@ country";
|
||||
"BoostGift.FromCountries_any" = "from %@ countries";
|
||||
"BoostGift.FromTwoCountries" = "from %1$@ and %2$@";
|
||||
"BoostGift.FromOneCountry" = "from %1$@";
|
||||
"BoostGift.FromAllCountries" = "from all countries";
|
||||
"BoostGift.AllSubscribers" = "All subscribers";
|
||||
"BoostGift.OnlyNewSubscribers" = "Only new subscribers";
|
||||
"BoostGift.LimitSubscribersInfo" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started.";
|
||||
"BoostGift.DateTitle" = "DATE WHEN GIVEAWAY ENDS";
|
||||
"BoostGift.DateEnds" = "Ends";
|
||||
"BoostGift.DateInfo" = "Choose when %1$@ of your channel will be randomly selected to receive Telegram Premium.";
|
||||
"BoostGift.DateInfoSubscribers_1" = "%@ subscriber";
|
||||
"BoostGift.DateInfoSubscribers_any" = "%@ subscribers";
|
||||
"BoostGift.DurationTitle" = "DURATION OF PREMIUM SUBSCRIPTIONS";
|
||||
"BoostGift.PremiumInfo" = "You can review the list of features and terms of use for Telegram Premium [here]().";
|
||||
"BoostGift.GiftPremium" = "Gift Premium";
|
||||
"BoostGift.StartGiveaway" = "Start Giveaway";
|
||||
"BoostGift.ReduceQuantity.Title" = "Reduce Quantity";
|
||||
"BoostGift.ReduceQuantity.Text" = "You can't acquire %1$@ %2$@-month subscriptions in the app. Do you want to reduce quantity to %3$@?";
|
||||
"BoostGift.ReduceQuantity.Reduce" = "Reduce";
|
||||
"BoostGift.GiveawayCreated.Title" = "Giveaway Created";
|
||||
"BoostGift.GiveawayCreated.Text" = "Check your channel's [Statistics]() to see how this giveaway boosted your channel.";
|
||||
"BoostGift.PremiumGifted.Title" = "Premium Subscriptions Gifted";
|
||||
"BoostGift.PremiumGifted.Text" = "Check your channel's [Statistics]() to see how gifts boosted your channel.";
|
||||
|
||||
"BoostGift.Subscribers.Title" = "Gift Premium";
|
||||
"BoostGift.Subscribers.Subtitle" = "select up to %@ subscribers";
|
||||
"BoostGift.Subscribers.SectionTitle" = "SUBSCRIBERS";
|
||||
"BoostGift.Subscribers.Joined" = "joined %@";
|
||||
"BoostGift.Subscribers.Search" = "Search Subscribers";
|
||||
"BoostGift.Subscribers.MaximumReached" = "You can select maximum %@ subscribers.";
|
||||
"BoostGift.Subscribers.Save" = "Save Recipients";
|
||||
|
||||
"BoostGift.Channels.Title" = "Add Channels";
|
||||
"BoostGift.Channels.Subtitle" = "select up to %@ channels";
|
||||
"BoostGift.Channels.SectionTitle" = "CHANNELS";
|
||||
"BoostGift.Channels.Search" = "Search Channels";
|
||||
"BoostGift.Channels.MaximumReached" = "You can select maximum %@ channels.";
|
||||
"BoostGift.Channels.PrivateChannel.Title" = "Channel is Private";
|
||||
"BoostGift.Channels.PrivateChannel.Text" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.";
|
||||
"BoostGift.Channels.PrivateChannel.Add" = "Add";
|
||||
"BoostGift.Channels.Save" = "Save Channels";
|
||||
|
||||
"Stats.Boosts.PrepaidGiveawaysTitle" = "PREPAID GIVEAWAYS";
|
||||
"Stats.Boosts.PrepaidGiveawayCount_1" = "%@ Telegram Premium";
|
||||
"Stats.Boosts.PrepaidGiveawayCount_any" = "%@ Telegram Premiums";
|
||||
"Stats.Boosts.PrepaidGiveawayMonths" = "%@-month subscriptions";
|
||||
"Stats.Boosts.PrepaidGiveawaysInfo" = "Select a giveaway you already paid for to set it up.";
|
||||
"Stats.Boosts.ShortMonth" = "%@m";
|
||||
"Stats.Boosts.Giveaway" = "Giveaway";
|
||||
"Stats.Boosts.Gift" = "Gift";
|
||||
"Stats.Boosts.TabBoosts_1" = "%@ Boost";
|
||||
"Stats.Boosts.TabBoosts_any" = "%@ Boosts";
|
||||
"Stats.Boosts.TabGifts_1" = "%@ Boost";
|
||||
"Stats.Boosts.TabGifts_any" = "%@ Boosts";
|
||||
"Stats.Boosts.ToBeDistributed" = "To Be Distributed";
|
||||
"Stats.Boosts.Unclaimed" = "Unclaimed";
|
||||
"Stats.Boosts.GetBoosts" = "Get Boosts via Gifts";
|
||||
"Stats.Boosts.GetBoostsInfo" = "Get more boosts for your channel by gifting Premium to your subscribers.";
|
||||
|
||||
"Notification.PremiumPrize.Title" = "Congratulations!";
|
||||
"Notification.PremiumPrize.UnclaimedText" = "You have an unclaimed prize from a giveaway by **%1$@**.\n\nThis prize is a **Telegram Premium** subscription for %2$@.";
|
||||
"Notification.PremiumPrize.GiveawayText" = "You won a prize in a giveaway organized by **%1$@**.\n\nYour prize is a **Telegram Premium** subscription for %2$@.";
|
||||
"Notification.PremiumPrize.GiftText" = "You've received a gift from **%1$@**.\n\nYour gift is a **Telegram Premium** subscription for %2$@.";
|
||||
"Notification.PremiumPrize.Months_1" = "**%@** month";
|
||||
"Notification.PremiumPrize.Months_any" = "**%@** months";
|
||||
"Notification.PremiumPrize.View" = "Open Gift Link";
|
||||
"Notification.PremiumPrize.Unclaimed" = "Unclaimed Prize";
|
||||
|
||||
"Story.SlideToSeek" = "Slide left or right to seek";
|
||||
"Story.Guide.Title" = "Watching Stories";
|
||||
"Story.Guide.Description" = "You can use these gestures to control playback.";
|
||||
"Story.Guide.ForwardTitle" = "Go forward";
|
||||
"Story.Guide.ForwardDescription" = "Tap the screen";
|
||||
"Story.Guide.PauseTitle" = "Pause and Seek";
|
||||
"Story.Guide.PauseDescription" = "Hold and move sideways";
|
||||
"Story.Guide.BackTitle" = "Go back";
|
||||
"Story.Guide.BackDescription" = "Tap the left edge";
|
||||
"Story.Guide.MoveTitle" = "Move between stories";
|
||||
"Story.Guide.MoveDescription" = "Swipe left or right";
|
||||
"Story.Guide.Proceed" = "Tap to keep watching";
|
||||
|
||||
"Chat.Giveaway.Info.Title" = "About This Giveaway";
|
||||
"Chat.Giveaway.Info.EndedTitle" = "Giveaway Ended";
|
||||
"Chat.Giveaway.Info.AlmostOver" = "The giveaway is almost over.";
|
||||
"Chat.Giveaway.Info.OngoingIntro" = "The giveaway is sponsored by the admins of **%1$@**, who acquired %2$@ for %3$@ for its followers.";
|
||||
"Chat.Giveaway.Info.OngoingNewMany" = "On **%1$@**, Telegram will automatically select %2$@ that joined **%3$@** and %4$@ after **%5$@**.";
|
||||
"Chat.Giveaway.Info.OngoingNew" = "On **%1$@**, Telegram will automatically select %2$@ that joined **%3$@** after **%4$@**.";
|
||||
"Chat.Giveaway.Info.OngoingMany" = "On **%1$@**, Telegram will automatically select %2$@ of **%3$@** and %4$@.";
|
||||
"Chat.Giveaway.Info.Ongoing" = "On **%1$@**, Telegram will automatically select %2$@ of **%3$@**.";
|
||||
"Chat.Giveaway.Info.EndedIntro" = "The giveaway was sponsored by the admins of **%1$@**, who acquired %2$@ for %3$@ for its followers.";
|
||||
"Chat.Giveaway.Info.EndedNewMany" = "On **%1$@**, Telegram automatically selected %2$@ that joined **%3$@** and other listed channels after **%4$@**.";
|
||||
"Chat.Giveaway.Info.EndedNew" = "On **%1$@**, Telegram automatically selected %2$@ that joined **%3$@** after **%4$@**.";
|
||||
"Chat.Giveaway.Info.EndedMany" = "On **%1$@**, Telegram automatically selected %2$@ of **%3$@** and other listed channels.";
|
||||
"Chat.Giveaway.Info.Ended" = "On **%1$@**, Telegram automatically selected %2$@ of **%3$@**.";
|
||||
"Chat.Giveaway.Info.NotAllowedJoinedEarly" = "You are not eligible to participate in this giveaway, because you joined this channel on **%@**, which is before the contest started.";
|
||||
"Chat.Giveaway.Info.NotAllowedAdmin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel (**%@**).";
|
||||
"Chat.Giveaway.Info.NotAllowedCountry" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway.";
|
||||
"Chat.Giveaway.Info.NotQualified" = "To take part in this giveaway please join the channel **%1$@** before **%2$@**.";
|
||||
"Chat.Giveaway.Info.NotQualifiedMany" = "To take part in this giveaway please join the channel **%1$@** (and %2$@) before **%3$@**.";
|
||||
"Chat.Giveaway.Info.Participating" = "You are participating in this giveaway, because you have joined the channel **%1$@**.";
|
||||
"Chat.Giveaway.Info.ParticipatingMany" = "You are participating in this giveaway, because you have joined the channel **%1$@** (and %2$@).";
|
||||
"Chat.Giveaway.Info.OtherChannels_1" = "**%@** other listed channel";
|
||||
"Chat.Giveaway.Info.OtherChannels_any" = "**%@** other listed channels";
|
||||
"Chat.Giveaway.Info.Subscriptions_1" = "**%@ Telegram Premium** subscription";
|
||||
"Chat.Giveaway.Info.Subscriptions_any" = "**%@ Telegram Premium** subscriptions";
|
||||
"Chat.Giveaway.Info.RandomUsers_1" = "**%@** random user";
|
||||
"Chat.Giveaway.Info.RandomUsers_any" = "**%@** random user";
|
||||
"Chat.Giveaway.Info.RandomSubscribers_1" = "**%@** random subscriber";
|
||||
"Chat.Giveaway.Info.RandomSubscribers_any" = "**%@** random subscribers";
|
||||
"Chat.Giveaway.Info.Months_1" = "**%@** month";
|
||||
"Chat.Giveaway.Info.Months_any" = "**%@** months";
|
||||
"Chat.Giveaway.Info.ActivatedLinks_1" = "%@ winner already used their gift link.";
|
||||
"Chat.Giveaway.Info.ActivatedLinks_any" = "%@ of the winners already used their gift links.";
|
||||
"Chat.Giveaway.Info.Refunded" = "The channel cancelled the prizes by reversing the payment for them.";
|
||||
"Chat.Giveaway.Info.Won" = "You won a prize in this giveaway. %@";
|
||||
"Chat.Giveaway.Info.DidntWin" = "You didn't win a prize in this giveaway.";
|
||||
"Chat.Giveaway.Info.ViewPrize" = "View My Prize";
|
||||
|
||||
"Chat.Giveaway.Toast.NotAllowed" = "You can't participate in this giveaway.";
|
||||
"Chat.Giveaway.Toast.Participating" = "You are participating in this giveaway.";
|
||||
"Chat.Giveaway.Toast.NotQualified" = "You are not qualified for this giveaway yet.";
|
||||
"Chat.Giveaway.Toast.AlmostOver" = "The giveaway is almost over.";
|
||||
"Chat.Giveaway.Toast.Ended" = "The giveaway is ended.";
|
||||
"Chat.Giveaway.Toast.LearnMore" = "Learn More";
|
||||
|
||||
"Chat.Giveaway.Message.PrizeTitle" = "Giveaway Prizes";
|
||||
"Chat.Giveaway.Message.PrizeText" = "%1$@ for %2$@.";
|
||||
"Chat.Giveaway.Message.Subscriptions_1" = "**%@** Telegram Premium Subscription";
|
||||
"Chat.Giveaway.Message.Subscriptions_any" = "**%@** Telegram Premium Subscriptions";
|
||||
"Chat.Giveaway.Message.Months_1" = "**%@** month";
|
||||
"Chat.Giveaway.Message.Months_any" = "**%@** months";
|
||||
"Chat.Giveaway.Message.ParticipantsTitle" = "Participants";
|
||||
"Chat.Giveaway.Message.ParticipantsNewMany" = "All users who join the channels below after this date:";
|
||||
"Chat.Giveaway.Message.ParticipantsNew" = "All users who join this channel after this date:";
|
||||
"Chat.Giveaway.Message.ParticipantsMany" = "All subscribers of the channels below:";
|
||||
"Chat.Giveaway.Message.Participants" = "All subscribers of this channel:";
|
||||
"Chat.Giveaway.Message.CountriesFrom" = "from %@";
|
||||
"Chat.Giveaway.Message.CountriesDelimiter" = ", ";
|
||||
"Chat.Giveaway.Message.CountriesLastDelimiter" = " and ";
|
||||
"Chat.Giveaway.Message.DateTitle" = "Winners Selection Date";
|
||||
"Chat.Giveaway.Message.LearnMore" = "LEARN MORE";
|
||||
|
||||
"GiftLink.Title" = "Gift Link";
|
||||
"GiftLink.UsedTitle" = "Used Gift Link";
|
||||
"GiftLink.Description" = "This link allows you to activate a **Telegram Premium** subscription.";
|
||||
"GiftLink.UsedDescription" = "This link was used to activate a **Telegram Premium** subscription.";
|
||||
"GiftLink.PersonalDescription" = "This link allows **%@** to activate a **Telegram Premium** subscription.";
|
||||
"GiftLink.PersonalUsedDescription" = "This link allowed **%@** to activate a **Telegram Premium** subscription.";
|
||||
"GiftLink.UnclaimedDescription" = "This link allows to activate a **Telegram Premium** subscription.";
|
||||
"GiftLink.Footer" = "You can also [send this link]() to a friend as a gift.";
|
||||
"GiftLink.UsedFooter" = "This link was used on %@.";
|
||||
"GiftLink.NotUsedFooter" = "This link hasn't been used yet.";
|
||||
"GiftLink.UseLink" = "Use Link";
|
||||
"GiftLink.Gift" = "Gift";
|
||||
"GiftLink.From" = "From";
|
||||
"GiftLink.To" = "To";
|
||||
"GiftLink.Reason" = "Reason";
|
||||
"GiftLink.Reason.Giveaway" = "Giveaway";
|
||||
"GiftLink.Reason.Gift" = "You were selected by the channel";
|
||||
"GiftLink.Reason.Unclaimed" = "Incomplete Giveaway";
|
||||
"GiftLink.Date" = "Date";
|
||||
"GiftLink.NoRecipient" = "No recipient";
|
||||
"GiftLink.TelegramPremium_1" = "Telegram Premium for %@ month";
|
||||
"GiftLink.TelegramPremium_any" = "Telegram Premium for %@ months";
|
||||
"GiftLink.LinkHidden" = "Only the recipient can see the link.";
|
||||
|
||||
@ -1044,6 +1044,7 @@ public protocol AccountContext: AnyObject {
|
||||
|
||||
var isPremium: Bool { get }
|
||||
var userLimits: EngineConfiguration.UserLimits { get }
|
||||
var peerNameColors: PeerNameColors { get }
|
||||
|
||||
var imageCache: AnyObject? { get }
|
||||
|
||||
@ -1062,19 +1063,21 @@ public protocol AccountContext: AnyObject {
|
||||
|
||||
public struct PremiumConfiguration {
|
||||
public static var defaultValue: PremiumConfiguration {
|
||||
return PremiumConfiguration(isPremiumDisabled: false, showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, giveawayGiftsPurchaseAvailable: false)
|
||||
return PremiumConfiguration(isPremiumDisabled: false, showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, giveawayGiftsPurchaseAvailable: false, boostsPerGiftCount: 3)
|
||||
}
|
||||
|
||||
public let isPremiumDisabled: Bool
|
||||
public let showPremiumGiftInAttachMenu: Bool
|
||||
public let showPremiumGiftInTextField: Bool
|
||||
public let giveawayGiftsPurchaseAvailable: Bool
|
||||
public let boostsPerGiftCount: Int32
|
||||
|
||||
fileprivate init(isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, giveawayGiftsPurchaseAvailable: Bool) {
|
||||
fileprivate init(isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, giveawayGiftsPurchaseAvailable: Bool, boostsPerGiftCount: Int32) {
|
||||
self.isPremiumDisabled = isPremiumDisabled
|
||||
self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu
|
||||
self.showPremiumGiftInTextField = showPremiumGiftInTextField
|
||||
self.giveawayGiftsPurchaseAvailable = giveawayGiftsPurchaseAvailable
|
||||
self.boostsPerGiftCount = boostsPerGiftCount
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> PremiumConfiguration {
|
||||
@ -1083,7 +1086,8 @@ public struct PremiumConfiguration {
|
||||
isPremiumDisabled: data["premium_purchase_blocked"] as? Bool ?? false,
|
||||
showPremiumGiftInAttachMenu: data["premium_gift_attach_menu_icon"] as? Bool ?? false,
|
||||
showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? false,
|
||||
giveawayGiftsPurchaseAvailable: data["giveaway_gifts_purchase_available"] as? Bool ?? false
|
||||
giveawayGiftsPurchaseAvailable: data["giveaway_gifts_purchase_available"] as? Bool ?? false,
|
||||
boostsPerGiftCount: Int32(data["boosts_per_sent_gift"] as? Double ?? 3)
|
||||
)
|
||||
} else {
|
||||
return .defaultValue
|
||||
@ -1197,3 +1201,136 @@ public struct StickersSearchConfiguration {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PeerNameColors: Equatable {
|
||||
public struct Colors: Equatable {
|
||||
public let main: UIColor
|
||||
public let secondary: UIColor?
|
||||
public let tertiary: UIColor?
|
||||
|
||||
init(main: UIColor, secondary: UIColor?, tertiary: UIColor?) {
|
||||
self.main = main
|
||||
self.secondary = secondary
|
||||
self.tertiary = tertiary
|
||||
}
|
||||
|
||||
init(main: UIColor) {
|
||||
self.main = main
|
||||
self.secondary = nil
|
||||
self.tertiary = nil
|
||||
}
|
||||
|
||||
init?(colors: [UIColor]) {
|
||||
guard let first = colors.first else {
|
||||
return nil
|
||||
}
|
||||
self.main = first
|
||||
if colors.count == 3 {
|
||||
self.secondary = colors[1]
|
||||
self.tertiary = colors[2]
|
||||
} else if colors.count == 2, let second = colors.last {
|
||||
self.secondary = second
|
||||
self.tertiary = nil
|
||||
} else {
|
||||
self.secondary = nil
|
||||
self.tertiary = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var defaultSingleColors: [Int32: Colors] {
|
||||
return [
|
||||
0: Colors(main: UIColor(rgb: 0xcc5049)),
|
||||
1: Colors(main: UIColor(rgb: 0xd67722)),
|
||||
2: Colors(main: UIColor(rgb: 0x955cdb)),
|
||||
3: Colors(main: UIColor(rgb: 0x40a920)),
|
||||
4: Colors(main: UIColor(rgb: 0x309eba)),
|
||||
5: Colors(main: UIColor(rgb: 0x368ad1)),
|
||||
6: Colors(main: UIColor(rgb: 0xc7508b))
|
||||
]
|
||||
}
|
||||
|
||||
public static var defaultValue: PeerNameColors {
|
||||
return PeerNameColors(
|
||||
colors: defaultSingleColors,
|
||||
darkColors: [:],
|
||||
displayOrder: [5, 3, 1, 0, 2, 4, 6]
|
||||
)
|
||||
}
|
||||
|
||||
public let colors: [Int32: Colors]
|
||||
public let darkColors: [Int32: Colors]
|
||||
public let displayOrder: [Int32]
|
||||
|
||||
public func get(_ color: PeerNameColor) -> Colors {
|
||||
if let colors = self.colors[color.rawValue] {
|
||||
return colors
|
||||
} else {
|
||||
return PeerNameColors.defaultSingleColors[5]!
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate init(colors: [Int32: Colors], darkColors: [Int32: Colors], displayOrder: [Int32]) {
|
||||
self.colors = colors
|
||||
self.darkColors = darkColors
|
||||
self.displayOrder = displayOrder
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> PeerNameColors {
|
||||
if let data = appConfiguration.data {
|
||||
var colors = PeerNameColors.defaultSingleColors
|
||||
var darkColors: [Int32: Colors] = [:]
|
||||
|
||||
if let peerColors = data["peer_colors"] as? [String: [String]] {
|
||||
for (key, values) in peerColors {
|
||||
if let index = Int32(key) {
|
||||
let colorsArray = values.compactMap { UIColor(hexString: $0) }
|
||||
if let colorValues = Colors(colors: colorsArray) {
|
||||
colors[index] = colorValues
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let darkPeerColors = data["dark_peer_colors"] as? [String: [String]] {
|
||||
for (key, values) in darkPeerColors {
|
||||
if let index = Int32(key) {
|
||||
let colorsArray = values.compactMap { UIColor(hexString: $0) }
|
||||
if let colorValues = Colors(colors: colorsArray) {
|
||||
darkColors[index] = colorValues
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var displayOrder: [Int32] = []
|
||||
if let order = data["peer_colors_available"] as? [Double] {
|
||||
displayOrder = order.map { Int32($0) }
|
||||
}
|
||||
if displayOrder.isEmpty {
|
||||
displayOrder = PeerNameColors.defaultValue.displayOrder
|
||||
}
|
||||
|
||||
return PeerNameColors(
|
||||
colors: colors,
|
||||
darkColors: darkColors,
|
||||
displayOrder: displayOrder
|
||||
)
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: PeerNameColors, rhs: PeerNameColors) -> Bool {
|
||||
if lhs.colors != rhs.colors {
|
||||
return false
|
||||
}
|
||||
if lhs.darkColors != rhs.darkColors {
|
||||
return false
|
||||
}
|
||||
if lhs.displayOrder != rhs.displayOrder {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,10 +339,15 @@ public final class ChatPresentationInterfaceState: Equatable {
|
||||
}
|
||||
|
||||
public struct AccountPeerColor: Equatable {
|
||||
public var isDashed: Bool
|
||||
public enum Style {
|
||||
case solid
|
||||
case doubleDashed
|
||||
case tripleDashed
|
||||
}
|
||||
public var style: Style
|
||||
|
||||
public init(isDashed: Bool) {
|
||||
self.isDashed = isDashed
|
||||
public init(style: Style) {
|
||||
self.style = style
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -278,7 +278,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
quote: ChatInputTextView.Theme.Quote(
|
||||
background: mainColor.withMultipliedAlpha(0.1),
|
||||
foreground: mainColor,
|
||||
isDashed: textInputView.theme?.quote.isDashed == true
|
||||
lineStyle: textInputView.theme?.quote.lineStyle ?? .solid
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -71,11 +71,13 @@ public final class TextNodeBlockQuoteData: NSObject {
|
||||
public let title: NSAttributedString?
|
||||
public let color: UIColor
|
||||
public let secondaryColor: UIColor?
|
||||
public let tertiaryColor: UIColor?
|
||||
|
||||
public init(title: NSAttributedString?, color: UIColor, secondaryColor: UIColor?) {
|
||||
public init(title: NSAttributedString?, color: UIColor, secondaryColor: UIColor?, tertiaryColor: UIColor?) {
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.secondaryColor = secondaryColor
|
||||
self.tertiaryColor = tertiaryColor
|
||||
|
||||
super.init()
|
||||
}
|
||||
@ -102,6 +104,13 @@ public final class TextNodeBlockQuoteData: NSObject {
|
||||
} else if (self.secondaryColor == nil) != (other.secondaryColor == nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsTertiaryColor = self.tertiaryColor, let rhsTertiaryColor = other.tertiaryColor {
|
||||
if !lhsTertiaryColor.isEqual(rhsTertiaryColor) {
|
||||
return false
|
||||
}
|
||||
} else if (self.tertiaryColor == nil) != (other.tertiaryColor == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -141,11 +150,13 @@ private final class TextNodeBlockQuote {
|
||||
let frame: CGRect
|
||||
let tintColor: UIColor
|
||||
let secondaryTintColor: UIColor?
|
||||
let tertiaryTintColor: UIColor?
|
||||
|
||||
init(frame: CGRect, tintColor: UIColor, secondaryTintColor: UIColor?) {
|
||||
init(frame: CGRect, tintColor: UIColor, secondaryTintColor: UIColor?, tertiaryTintColor: UIColor?) {
|
||||
self.frame = frame
|
||||
self.tintColor = tintColor
|
||||
self.secondaryTintColor = secondaryTintColor
|
||||
self.tertiaryTintColor = tertiaryTintColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -1212,6 +1223,7 @@ open class TextNode: ASDisplayNode {
|
||||
let isBlockQuote: Bool
|
||||
let tintColor: UIColor?
|
||||
let secondaryTintColor: UIColor?
|
||||
let tertiaryTintColor: UIColor?
|
||||
}
|
||||
var stringSegments: [StringSegment] = []
|
||||
|
||||
@ -1235,7 +1247,8 @@ open class TextNode: ASDisplayNode {
|
||||
firstCharacterOffset: segmentCharacterOffset,
|
||||
isBlockQuote: false,
|
||||
tintColor: nil,
|
||||
secondaryTintColor: nil
|
||||
secondaryTintColor: nil,
|
||||
tertiaryTintColor: nil
|
||||
))
|
||||
}
|
||||
|
||||
@ -1247,7 +1260,8 @@ open class TextNode: ASDisplayNode {
|
||||
firstCharacterOffset: effectiveRange.location,
|
||||
isBlockQuote: true,
|
||||
tintColor: value.color,
|
||||
secondaryTintColor: value.secondaryColor
|
||||
secondaryTintColor: value.secondaryColor,
|
||||
tertiaryTintColor: value.tertiaryColor
|
||||
))
|
||||
}
|
||||
segmentCharacterOffset = effectiveRange.location + effectiveRange.length
|
||||
@ -1261,7 +1275,8 @@ open class TextNode: ASDisplayNode {
|
||||
firstCharacterOffset: effectiveRange.location,
|
||||
isBlockQuote: false,
|
||||
tintColor: nil,
|
||||
secondaryTintColor: nil
|
||||
secondaryTintColor: nil,
|
||||
tertiaryTintColor: nil
|
||||
))
|
||||
segmentCharacterOffset = effectiveRange.location + effectiveRange.length
|
||||
}
|
||||
@ -1277,7 +1292,8 @@ open class TextNode: ASDisplayNode {
|
||||
firstCharacterOffset: segmentCharacterOffset,
|
||||
isBlockQuote: false,
|
||||
tintColor: nil,
|
||||
secondaryTintColor: nil
|
||||
secondaryTintColor: nil,
|
||||
tertiaryTintColor: nil
|
||||
))
|
||||
}
|
||||
|
||||
@ -1290,6 +1306,7 @@ open class TextNode: ASDisplayNode {
|
||||
var lines: [TextNodeLine] = []
|
||||
var tintColor: UIColor?
|
||||
var secondaryTintColor: UIColor?
|
||||
var tertiaryTintColor: UIColor?
|
||||
var isBlockQuote: Bool = false
|
||||
var additionalWidth: CGFloat = 0.0
|
||||
}
|
||||
@ -1301,6 +1318,7 @@ open class TextNode: ASDisplayNode {
|
||||
calculatedSegment.isBlockQuote = segment.isBlockQuote
|
||||
calculatedSegment.tintColor = segment.tintColor
|
||||
calculatedSegment.secondaryTintColor = segment.secondaryTintColor
|
||||
calculatedSegment.tertiaryTintColor = segment.tertiaryTintColor
|
||||
|
||||
let rawSubstring = segment.substring.string as NSString
|
||||
let substringLength = rawSubstring.length
|
||||
@ -1512,7 +1530,7 @@ open class TextNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if segment.isBlockQuote, let tintColor = segment.tintColor {
|
||||
blockQuotes.append(TextNodeBlockQuote(frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY - 2.0), size: CGSize(width: blockWidth, height: blockMaxY - (blockMinY - 2.0) + 4.0)), tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor))
|
||||
blockQuotes.append(TextNodeBlockQuote(frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY - 2.0), size: CGSize(width: blockWidth, height: blockMaxY - (blockMinY - 2.0) + 4.0)), tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor, tertiaryTintColor: segment.tertiaryTintColor))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2216,9 +2234,19 @@ open class TextNode: ASDisplayNode {
|
||||
if let secondaryTintColor = blockQuote.secondaryTintColor {
|
||||
let isMonochrome = secondaryTintColor.alpha == 0.0
|
||||
|
||||
let tertiaryTintColor = blockQuote.tertiaryTintColor
|
||||
let dashHeight: CGFloat = tertiaryTintColor != nil ? 6.0 : 9.0
|
||||
|
||||
do {
|
||||
context.saveGState()
|
||||
|
||||
|
||||
let dashOffset: CGFloat
|
||||
if let _ = tertiaryTintColor {
|
||||
dashOffset = isMonochrome ? -2.0 : 0.0
|
||||
} else {
|
||||
dashOffset = isMonochrome ? -4.0 : 5.0
|
||||
}
|
||||
|
||||
if isMonochrome {
|
||||
context.setFillColor(blockQuote.tintColor.withMultipliedAlpha(0.2).cgColor)
|
||||
context.fill(lineFrame)
|
||||
@ -2229,23 +2257,37 @@ open class TextNode: ASDisplayNode {
|
||||
context.setFillColor(secondaryTintColor.cgColor)
|
||||
}
|
||||
|
||||
let dashOffset: CGFloat = isMonochrome ? -4.0 : 5.0
|
||||
context.translateBy(x: blockFrame.minX, y: blockFrame.minY + dashOffset)
|
||||
|
||||
var offset = 0.0
|
||||
while offset < blockFrame.height {
|
||||
context.move(to: CGPoint(x: 0.0, y: 3.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 0.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: 9.0 + 3.0))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
func drawDashes() {
|
||||
context.translateBy(x: blockFrame.minX, y: blockFrame.minY + dashOffset)
|
||||
|
||||
context.translateBy(x: 0.0, y: 18.0)
|
||||
offset += 18.0
|
||||
var offset = 0.0
|
||||
while offset < blockFrame.height {
|
||||
context.move(to: CGPoint(x: 0.0, y: 3.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 0.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: dashHeight))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: dashHeight + 3.0))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
|
||||
context.translateBy(x: 0.0, y: 18.0)
|
||||
offset += 18.0
|
||||
}
|
||||
}
|
||||
|
||||
drawDashes()
|
||||
context.restoreGState()
|
||||
|
||||
if let tertiaryTintColor {
|
||||
context.saveGState()
|
||||
context.translateBy(x: 0.0, y: dashHeight)
|
||||
if isMonochrome {
|
||||
context.setFillColor(blockQuote.tintColor.withAlphaComponent(0.4).cgColor)
|
||||
} else {
|
||||
context.setFillColor(tertiaryTintColor.cgColor)
|
||||
}
|
||||
drawDashes()
|
||||
context.restoreGState()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.setFillColor(blockQuote.tintColor.cgColor)
|
||||
|
||||
@ -108,6 +108,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||
"//submodules/CountrySelectionUI",
|
||||
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||
"//submodules/InvisibleInkDustNode",
|
||||
"//submodules/AlertUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -38,7 +38,6 @@ final class AppIconsDemoComponent: Component {
|
||||
private var component: AppIconsDemoComponent?
|
||||
|
||||
private var containerView: UIView
|
||||
private var axisView = UIView()
|
||||
private var imageViews: [UIImageView] = []
|
||||
|
||||
private var isVisible = false
|
||||
@ -50,7 +49,6 @@ final class AppIconsDemoComponent: Component {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.containerView)
|
||||
self.containerView.addSubview(self.axisView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -64,11 +62,7 @@ final class AppIconsDemoComponent: Component {
|
||||
|
||||
self.containerView.frame = CGRect(origin: CGPoint(x: -availableSize.width / 2.0, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
|
||||
|
||||
self.axisView.bounds = CGRect(origin: .zero, size: availableSize)
|
||||
self.axisView.center = CGPoint(x: availableSize.width, y: availableSize.height / 2.0)
|
||||
|
||||
if self.imageViews.isEmpty {
|
||||
var i = 0
|
||||
for icon in component.appIcons {
|
||||
let image: UIImage?
|
||||
switch icon.imageName {
|
||||
@ -78,12 +72,6 @@ final class AppIconsDemoComponent: Component {
|
||||
image = UIImage(bundleImageName: "Premium/Icons/Black")
|
||||
case "PremiumTurbo":
|
||||
image = UIImage(bundleImageName: "Premium/Icons/Turbo")
|
||||
case "PremiumDuck":
|
||||
image = UIImage(bundleImageName: "Premium/Icons/Duck")
|
||||
case "PremiumCoffee":
|
||||
image = UIImage(bundleImageName: "Premium/Icons/Coffee")
|
||||
case "PremiumSteam":
|
||||
image = UIImage(bundleImageName: "Premium/Icons/Steam")
|
||||
default:
|
||||
image = nil
|
||||
}
|
||||
@ -95,37 +83,31 @@ final class AppIconsDemoComponent: Component {
|
||||
imageView.layer.cornerCurve = .continuous
|
||||
}
|
||||
imageView.image = image
|
||||
if i == 0 {
|
||||
self.containerView.addSubview(imageView)
|
||||
} else {
|
||||
self.axisView.addSubview(imageView)
|
||||
}
|
||||
self.containerView.addSubview(imageView)
|
||||
|
||||
self.imageViews.append(imageView)
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let radius: CGFloat = availableSize.width * 0.33
|
||||
let angleIncrement: CGFloat = 2 * .pi / CGFloat(self.imageViews.count - 1)
|
||||
|
||||
var i = 0
|
||||
for view in self.imageViews {
|
||||
let position: CGPoint
|
||||
if i == 0 {
|
||||
position = CGPoint(x: availableSize.width, y: availableSize.height / 2.0)
|
||||
} else {
|
||||
let angle = CGFloat(i - 1) * angleIncrement
|
||||
let xPosition = radius * cos(angle) + availableSize.width / 2.0
|
||||
let yPosition = radius * sin(angle) + availableSize.height / 2.0
|
||||
|
||||
position = CGPoint(x: xPosition, y: yPosition)
|
||||
switch i {
|
||||
case 0:
|
||||
position = CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.333)
|
||||
case 1:
|
||||
position = CGPoint(x: availableSize.width * 0.333, y: availableSize.height * 0.667)
|
||||
case 2:
|
||||
position = CGPoint(x: availableSize.width * 0.667, y: availableSize.height * 0.667)
|
||||
default:
|
||||
position = CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5)
|
||||
}
|
||||
|
||||
if !self.animating {
|
||||
view.center = position.offsetBy(dx: availableSize.width / 2.0, dy: 0.0)
|
||||
}
|
||||
|
||||
view.center = position
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
@ -143,48 +125,6 @@ final class AppIconsDemoComponent: Component {
|
||||
}
|
||||
self.isVisible = isDisplaying
|
||||
|
||||
let rotationDuration: Double = 12.0
|
||||
if isDisplaying {
|
||||
if self.axisView.layer.animation(forKey: "rotationAnimation") == nil {
|
||||
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
|
||||
rotationAnimation.fromValue = 0.0
|
||||
rotationAnimation.toValue = 2.0 * CGFloat.pi
|
||||
rotationAnimation.duration = rotationDuration
|
||||
rotationAnimation.repeatCount = Float.infinity
|
||||
self.axisView.layer.add(rotationAnimation, forKey: "rotationAnimation")
|
||||
|
||||
var i = 0
|
||||
for view in self.imageViews {
|
||||
if i == 0 {
|
||||
let animation = CABasicAnimation(keyPath: "transform.scale")
|
||||
animation.duration = 2.0
|
||||
animation.fromValue = 1.0
|
||||
animation.toValue = 1.15
|
||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
animation.autoreverses = true
|
||||
animation.repeatCount = .infinity
|
||||
view.layer.add(animation, forKey: "scale")
|
||||
} else {
|
||||
view.transform = CGAffineTransformMakeScale(0.8, 0.8)
|
||||
|
||||
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
|
||||
rotationAnimation.fromValue = 0.0
|
||||
rotationAnimation.toValue = -2.0 * CGFloat.pi
|
||||
rotationAnimation.duration = rotationDuration
|
||||
rotationAnimation.repeatCount = Float.infinity
|
||||
view.layer.add(rotationAnimation, forKey: "rotationAnimation")
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.axisView.layer.removeAllAnimations()
|
||||
for view in self.imageViews {
|
||||
view.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
@ -192,37 +132,38 @@ final class AppIconsDemoComponent: Component {
|
||||
func animateIn(availableSize: CGSize) {
|
||||
self.animating = true
|
||||
|
||||
let radius: CGFloat = availableSize.width * 2.5
|
||||
let angleIncrement: CGFloat = 2 * .pi / CGFloat(self.imageViews.count - 1)
|
||||
|
||||
var i = 0
|
||||
for view in self.imageViews {
|
||||
if i > 0 {
|
||||
let delay: Double = 0.033 * Double(i - 1)
|
||||
|
||||
let angle = CGFloat(i - 1) * angleIncrement
|
||||
let xPosition = radius * cos(angle)
|
||||
let yPosition = radius * sin(angle)
|
||||
|
||||
let from = CGPoint(x: xPosition, y: yPosition)
|
||||
let initialPosition = view.layer.position
|
||||
view.layer.position = initialPosition.offsetBy(dx: xPosition, dy: yPosition)
|
||||
view.alpha = 0.0
|
||||
|
||||
Queue.mainQueue().after(delay) {
|
||||
view.alpha = 1.0
|
||||
view.layer.position = initialPosition
|
||||
view.layer.animateScale(from: 3.0, to: 0.8, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
view.layer.animatePosition(from: from, to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
|
||||
if i == self.imageViews.count - 1 {
|
||||
self.animating = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let from: CGPoint
|
||||
let delay: Double
|
||||
switch i {
|
||||
case 0:
|
||||
from = CGPoint(x: -availableSize.width * 0.333, y: -availableSize.height * 0.8)
|
||||
delay = 0.1
|
||||
case 1:
|
||||
from = CGPoint(x: -availableSize.width * 0.55, y: availableSize.height * 0.75)
|
||||
delay = 0.15
|
||||
case 2:
|
||||
from = CGPoint(x: availableSize.width * 0.9, y: availableSize.height * 0.75)
|
||||
delay = 0.0
|
||||
default:
|
||||
from = CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5)
|
||||
delay = 0.0
|
||||
}
|
||||
|
||||
let initialPosition = view.layer.position
|
||||
view.layer.position = initialPosition.offsetBy(dx: from.x, dy: from.y)
|
||||
|
||||
Queue.mainQueue().after(delay) {
|
||||
view.layer.position = initialPosition
|
||||
view.layer.animateScale(from: 3.0, to: 1.0, duration: 0.5, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
view.layer.animatePosition(from: from, to: CGPoint(), duration: 0.5, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
|
||||
if i == 2 {
|
||||
self.animating = false
|
||||
}
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,7 +361,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
default:
|
||||
color = .blue
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: nil)
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, sectionId: self.section, action: nil)
|
||||
case let .subscriptionsHeader(_, text, additionalText):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
|
||||
case let .subscriptions(_, value):
|
||||
@ -377,8 +377,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case let .channelsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .channel(_, _, peer, boosts, isRevealed):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text("this channel will receive \($0) boosts", .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
// arguments.openPeer(peer)
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
}, setPeerIdWithRevealedOptions: { lhs, rhs in
|
||||
arguments.setItemIdWithRevealedOptions(lhs, rhs)
|
||||
}, removePeer: { id in
|
||||
@ -433,7 +432,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever
|
||||
}
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Ends", label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
arguments.dismissInput()
|
||||
var focus = false
|
||||
arguments.updateState { state in
|
||||
@ -515,7 +514,7 @@ private func createGiveawayControllerEntries(
|
||||
|
||||
switch subject {
|
||||
case .generic:
|
||||
entries.append(.createGiveaway(presentationData.theme, "Create Giveaway", "winners are chosen randomly", state.mode == .giveaway))
|
||||
entries.append(.createGiveaway(presentationData.theme, presentationData.strings.BoostGift_CreateGiveaway, presentationData.strings.BoostGift_CreateGiveawayInfo, state.mode == .giveaway))
|
||||
|
||||
let recipientsText: String
|
||||
if !state.peers.isEmpty {
|
||||
@ -533,59 +532,68 @@ private func createGiveawayControllerEntries(
|
||||
recipientsText = presentationData.strings.PremiumGift_LabelRecipients(Int32(peersCount))
|
||||
}
|
||||
} else {
|
||||
recipientsText = "select recipients"
|
||||
recipientsText = presentationData.strings.BoostGift_SelectRecipients
|
||||
}
|
||||
entries.append(.awardUsers(presentationData.theme, "Award Specific Users", recipientsText, state.mode == .gift))
|
||||
entries.append(.awardUsers(presentationData.theme, presentationData.strings.BoostGift_AwardSpecificUsers, recipientsText, state.mode == .gift))
|
||||
case let .prepaid(prepaidGiveaway):
|
||||
entries.append(.prepaidHeader(presentationData.theme, "PREPAID GIVEAWAY"))
|
||||
entries.append(.prepaid(presentationData.theme, "\(prepaidGiveaway.quantity) Telegram Premium", "\(prepaidGiveaway.months)-month subscriptions", prepaidGiveaway))
|
||||
entries.append(.prepaidHeader(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayTitle))
|
||||
entries.append(.prepaid(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity), presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(prepaidGiveaway.months)").string, prepaidGiveaway))
|
||||
}
|
||||
|
||||
if case .giveaway = state.mode {
|
||||
if case .generic = subject {
|
||||
entries.append(.subscriptionsHeader(presentationData.theme, "QUANTITY OF PRIZES".uppercased(), "\(state.subscriptions) BOOSTS"))
|
||||
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_QuantityTitle.uppercased(), presentationData.strings.BoostGift_QuantityBoosts(state.subscriptions * 4)))
|
||||
entries.append(.subscriptions(presentationData.theme, state.subscriptions))
|
||||
entries.append(.subscriptionsInfo(presentationData.theme, "Choose how many Premium subscriptions to give away and boosts to receive."))
|
||||
entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo))
|
||||
}
|
||||
|
||||
entries.append(.channelsHeader(presentationData.theme, "CHANNELS INCLUDED IN THE GIVEAWAY".uppercased()))
|
||||
entries.append(.channelsHeader(presentationData.theme, presentationData.strings.BoostGift_ChannelsTitle.uppercased()))
|
||||
var index: Int32 = 0
|
||||
let channels = [peerId] + state.channels
|
||||
for channelId in channels {
|
||||
if let channel = peers[channelId] {
|
||||
entries.append(.channel(index, presentationData.theme, channel, channel.id == peerId ? state.subscriptions : nil, false))
|
||||
entries.append(.channel(index, presentationData.theme, channel, channel.id == peerId ? state.subscriptions * 4 : nil, false))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
entries.append(.channelAdd(presentationData.theme, "Add Channel"))
|
||||
entries.append(.channelsInfo(presentationData.theme, "Choose the channels users need to be subscribed to take part in the giveaway"))
|
||||
entries.append(.channelAdd(presentationData.theme, presentationData.strings.BoostGift_AddChannel))
|
||||
entries.append(.channelsInfo(presentationData.theme, presentationData.strings.BoostGift_ChannelsInfo))
|
||||
|
||||
entries.append(.usersHeader(presentationData.theme, "USERS ELIGIBLE FOR THE GIVEAWAY".uppercased()))
|
||||
entries.append(.usersHeader(presentationData.theme, presentationData.strings.BoostGift_UsersTitle.uppercased()))
|
||||
|
||||
let countriesText: String
|
||||
if state.countries.count > 2 {
|
||||
countriesText = "from \(state.countries.count) countries"
|
||||
countriesText = presentationData.strings.BoostGift_FromCountries(Int32(state.countries.count))
|
||||
} else if !state.countries.isEmpty {
|
||||
let allCountries = state.countries.map { locale.localizedString(forRegionCode: $0) ?? $0 }.joined(separator: " and ")
|
||||
countriesText = "from \(allCountries)"
|
||||
if state.countries.count == 2 {
|
||||
let firstCountryCode = state.countries.first ?? ""
|
||||
let secondCountryCode = state.countries.last ?? ""
|
||||
let firstCountryName = locale.localizedString(forRegionCode: firstCountryCode) ?? firstCountryCode
|
||||
let secondCountryName = locale.localizedString(forRegionCode: secondCountryCode) ?? secondCountryCode
|
||||
countriesText = presentationData.strings.BoostGift_FromTwoCountries(firstCountryName, secondCountryName).string
|
||||
} else {
|
||||
let countryCode = state.countries.first ?? ""
|
||||
let countryName = locale.localizedString(forRegionCode: countryCode) ?? countryCode
|
||||
countriesText = presentationData.strings.BoostGift_FromOneCountry(countryName).string
|
||||
}
|
||||
} else {
|
||||
countriesText = "from all countries"
|
||||
countriesText = presentationData.strings.BoostGift_FromAllCountries
|
||||
}
|
||||
|
||||
entries.append(.usersAll(presentationData.theme, "All subscribers", countriesText, !state.onlyNewEligible))
|
||||
entries.append(.usersNew(presentationData.theme, "Only new subscribers", countriesText, state.onlyNewEligible))
|
||||
entries.append(.usersInfo(presentationData.theme, "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started."))
|
||||
entries.append(.usersAll(presentationData.theme, presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible))
|
||||
entries.append(.usersNew(presentationData.theme, presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible))
|
||||
entries.append(.usersInfo(presentationData.theme, presentationData.strings.BoostGift_LimitSubscribersInfo))
|
||||
|
||||
entries.append(.timeHeader(presentationData.theme, "DATE WHEN GIVEAWAY ENDS".uppercased()))
|
||||
entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased()))
|
||||
entries.append(.timeExpiryDate(presentationData.theme, presentationData.dateTimeFormat, state.time, state.pickingTimeLimit))
|
||||
if state.pickingTimeLimit {
|
||||
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate))
|
||||
}
|
||||
entries.append(.timeInfo(presentationData.theme, "Choose when \(state.subscriptions) subscribers of your channel will be randomly selected to receive Telegram Premium."))
|
||||
entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string))
|
||||
}
|
||||
|
||||
if case .generic = subject {
|
||||
entries.append(.durationHeader(presentationData.theme, "DURATION OF PREMIUM SUBSCRIPTIONS".uppercased()))
|
||||
entries.append(.durationHeader(presentationData.theme, presentationData.strings.BoostGift_DurationTitle.uppercased()))
|
||||
|
||||
let recipientCount: Int
|
||||
switch state.mode {
|
||||
@ -628,7 +636,7 @@ private func createGiveawayControllerEntries(
|
||||
i += 1
|
||||
}
|
||||
|
||||
entries.append(.durationInfo(presentationData.theme, "You can review the list of features and terms of use for Telegram Premium [here]()."))
|
||||
entries.append(.durationInfo(presentationData.theme, presentationData.strings.BoostGift_PremiumInfo))
|
||||
}
|
||||
|
||||
return entries
|
||||
@ -776,7 +784,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let (state, peersMap) = stateAndPeersMap
|
||||
|
||||
let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, title: "Boosts via Gifts", text: "Get more boosts for your channel by gifting\nPremium to your subscribers.", cancel: {
|
||||
let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: presentationData.strings.BoostGift_Description, cancel: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
@ -787,7 +795,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
case .gift:
|
||||
badgeCount = Int32(state.peers.count)
|
||||
}
|
||||
let footerItem = CreateGiveawayFooterItem(theme: presentationData.theme, title: state.mode == .gift ? "Gift Premium" : "Start Giveaway", badgeCount: badgeCount, isLoading: state.updating, action: {
|
||||
let footerItem = CreateGiveawayFooterItem(theme: presentationData.theme, title: state.mode == .gift ? presentationData.strings.BoostGift_GiftPremium : presentationData.strings.BoostGift_StartGiveaway, badgeCount: badgeCount, isLoading: state.updating, action: {
|
||||
buyActionImpl?()
|
||||
})
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {})
|
||||
@ -857,7 +865,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
|
||||
guard let selectedProduct else {
|
||||
let alertController = textAlertController(context: context, title: "Reduce Quantity", text: "You can't acquire \(state.subscriptions) \(selectedMonths)-month subscriptions in the app. Do you want to reduce quantity to 25?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: "Reduce", action: {
|
||||
let alertController = textAlertController(context: context, title: presentationData.strings.BoostGift_ReduceQuantity_Title, text: presentationData.strings.BoostGift_ReduceQuantity_Text("\(state.subscriptions)", "\(selectedMonths)", "\(25)").string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BoostGift_ReduceQuantity_Reduce, action: {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.subscriptions = 25
|
||||
@ -915,11 +923,11 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
let text: String
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
title = "Giveaway Created"
|
||||
text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
||||
title = presentationData.strings.BoostGift_GiveawayCreated_Title
|
||||
text = presentationData.strings.BoostGift_GiveawayCreated_Text
|
||||
case .gift:
|
||||
title = "Premium Subscriptions Gifted"
|
||||
text = "Check your channel's [Statistics]() to see how gifts boosted your channel."
|
||||
title = presentationData.strings.BoostGift_PremiumGifted_Title
|
||||
text = presentationData.strings.BoostGift_PremiumGifted_Text
|
||||
}
|
||||
|
||||
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in
|
||||
@ -992,8 +1000,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
controllers.removeLast(count)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
let title = "Giveaway Created"
|
||||
let text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
||||
let title = presentationData.strings.BoostGift_GiveawayCreated_Title
|
||||
let text = presentationData.strings.BoostGift_GiveawayCreated_Text
|
||||
|
||||
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: peerId))
|
||||
|
||||
@ -11,12 +11,14 @@ import ComponentFlow
|
||||
|
||||
final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let title: String
|
||||
let text: String
|
||||
let cancel: () -> Void
|
||||
|
||||
init(theme: PresentationTheme, title: String, text: String, cancel: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, title: String, text: String, cancel: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.cancel = cancel
|
||||
@ -132,7 +134,7 @@ class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode {
|
||||
self.titleNode.attributedText = attributedTitle
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.cancelNode.setAttributedTitle(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: self.item.theme.rootController.navigationBar.accentTextColor), for: .normal)
|
||||
self.cancelNode.setAttributedTitle(NSAttributedString(string: self.item.strings.Common_Cancel, font: Font.regular(17.0), textColor: self.item.theme.rootController.navigationBar.accentTextColor), for: .normal)
|
||||
}
|
||||
|
||||
override func updateContentOffset(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
@ -4,12 +4,164 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import TelegramStringFormatting
|
||||
import TelegramPresentationData
|
||||
import Markdown
|
||||
import AlertUI
|
||||
|
||||
public func giveawayInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, message: EngineMessage, giveawayInfo: PremiumGiveawayInfo) -> ViewController? {
|
||||
guard let giveaway = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var peerName = ""
|
||||
if let peerId = giveaway.channelPeerIds.first, let peer = message.peers[peerId] {
|
||||
peerName = EnginePeer(peer).compactDisplayTitle
|
||||
}
|
||||
|
||||
let untilDate = stringForDate(timestamp: giveaway.untilDate, strings: presentationData.strings)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
var warning: String?
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
var actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
|
||||
switch giveawayInfo {
|
||||
case let .ongoing(start, status):
|
||||
let startDate = stringForDate(timestamp: start, strings: presentationData.strings)
|
||||
|
||||
title = presentationData.strings.Chat_Giveaway_Info_Title
|
||||
|
||||
let intro: String
|
||||
if case .almostOver = status {
|
||||
intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(giveaway.quantity), presentationData.strings.Chat_Giveaway_Info_Months(giveaway.months)).string
|
||||
} else {
|
||||
intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(giveaway.quantity), presentationData.strings.Chat_Giveaway_Info_Months(giveaway.months)).string
|
||||
}
|
||||
|
||||
let ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
let randomUsers = presentationData.strings.Chat_Giveaway_Info_RandomUsers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingNewMany(untilDate, randomUsers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1)), startDate).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingNew(untilDate, randomUsers, peerName, startDate).string
|
||||
}
|
||||
} else {
|
||||
let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingMany(untilDate, randomSubscribers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1))).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_Ongoing(untilDate, randomSubscribers, peerName).string
|
||||
}
|
||||
}
|
||||
|
||||
var participation: String
|
||||
switch status {
|
||||
case .notQualified:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotQualifiedMany(peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1)), untilDate).string
|
||||
} else {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotQualified(peerName, untilDate).string
|
||||
}
|
||||
case let .notAllowed(reason):
|
||||
switch reason {
|
||||
case let .joinedTooEarly(joinedOn):
|
||||
let joinDate = stringForDate(timestamp: joinedOn, strings: presentationData.strings)
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedJoinedEarly(joinDate).string
|
||||
case let .channelAdmin(adminId):
|
||||
let _ = adminId
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedAdmin(peerName).string
|
||||
case .disallowedCountry:
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedCountry
|
||||
}
|
||||
case .participating:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_ParticipatingMany(peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1))).string
|
||||
} else {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_Participating(peerName).string
|
||||
}
|
||||
case .almostOver:
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_AlmostOver
|
||||
}
|
||||
|
||||
if !participation.isEmpty {
|
||||
participation = "\n\n\(participation)"
|
||||
}
|
||||
|
||||
text = "\(intro)\n\n\(ending)\(participation)"
|
||||
case let .finished(status, start, finish, _, activatedCount):
|
||||
let startDate = stringForDate(timestamp: start, strings: presentationData.strings)
|
||||
let finishDate = stringForDate(timestamp: finish, strings: presentationData.strings)
|
||||
title = presentationData.strings.Chat_Giveaway_Info_EndedTitle
|
||||
|
||||
let intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(giveaway.quantity), presentationData.strings.Chat_Giveaway_Info_Months(giveaway.months)).string
|
||||
|
||||
var ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
let randomUsers = presentationData.strings.Chat_Giveaway_Info_RandomUsers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_EndedNewMany(finishDate, randomUsers, peerName, startDate).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_EndedNew(finishDate, randomUsers, peerName, startDate).string
|
||||
}
|
||||
} else {
|
||||
let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_EndedMany(finishDate, randomSubscribers, peerName).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_Ended(finishDate, randomSubscribers, peerName).string
|
||||
}
|
||||
}
|
||||
|
||||
if activatedCount > 0 {
|
||||
ending += " " + presentationData.strings.Chat_Giveaway_Info_ActivatedLinks(activatedCount)
|
||||
}
|
||||
|
||||
var result: String
|
||||
switch status {
|
||||
case .refunded:
|
||||
result = ""
|
||||
warning = presentationData.strings.Chat_Giveaway_Info_Refunded
|
||||
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Close, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
case .notWon:
|
||||
result = "\n\n" + presentationData.strings.Chat_Giveaway_Info_DidntWin
|
||||
case let .won(slug):
|
||||
result = "\n\n" + presentationData.strings.Chat_Giveaway_Info_Won("🏆").string
|
||||
let _ = slug
|
||||
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Chat_Giveaway_Info_ViewPrize, action: {
|
||||
dismissImpl?()
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
}
|
||||
|
||||
text = "\(intro)\n\n\(ending)\(result)"
|
||||
}
|
||||
|
||||
let alertController = giveawayInfoAlertController(
|
||||
context: context,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
title: title,
|
||||
text: text,
|
||||
warning: warning,
|
||||
actions: actions
|
||||
)
|
||||
dismissImpl = { [weak alertController] in
|
||||
alertController?.dismissAnimated()
|
||||
}
|
||||
return alertController
|
||||
}
|
||||
|
||||
private final class GiveawayInfoAlertContentNode: AlertContentNode {
|
||||
private let title: String
|
||||
@ -229,7 +381,7 @@ private final class GiveawayInfoAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
func giveawayInfoAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, title: String, text: String, warning: String?, actions: [TextAlertAction]) -> AlertController {
|
||||
private func giveawayInfoAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, title: String, text: String, warning: String?, actions: [TextAlertAction]) -> AlertController {
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let contentNode = GiveawayInfoAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, title: title, text: text, warning: warning, actions: actions)
|
||||
@ -8,8 +8,6 @@ import TelegramPresentationData
|
||||
import UndoUI
|
||||
import PresentationDataUtils
|
||||
|
||||
//TODO:localize
|
||||
|
||||
private struct BoostState {
|
||||
let level: Int32
|
||||
let currentLevelBoosts: Int32
|
||||
@ -106,7 +104,7 @@ public func PremiumBoostScreen(
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostReplaceIcon"), color: .white)!, title: nil, text: "\(replacedBoosts) boosts are reassigned from \(inChannels) other channel.", round: false, undoText: nil), elevatedLayout: false, position: .bottom, action: { _ in return true })
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostReplaceIcon"), color: .white)!, title: nil, text: presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), presentationData.strings.ReassignBoost_OtherChannels(inChannels)).string, round: false, undoText: nil), elevatedLayout: false, position: .bottom, action: { _ in return true })
|
||||
controller.present(undoController, in: .current)
|
||||
}
|
||||
}
|
||||
@ -182,11 +180,12 @@ public func PremiumBoostScreen(
|
||||
}
|
||||
} else {
|
||||
if isPremium {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 }))
|
||||
let controller = textAlertController(
|
||||
sharedContext: context.sharedContext,
|
||||
updatedPresentationData: nil,
|
||||
title: "More Boosts Needed",
|
||||
text: "To boost **\(peer.compactDisplayTitle)**, get more boosts by gifting **Telegram Premium** to a friend.",
|
||||
title: presentationData.strings.ChannelBoost_MoreBoosts_Title,
|
||||
text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
|
||||
],
|
||||
|
||||
@ -20,44 +20,48 @@ import AvatarNode
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import UndoUI
|
||||
import InvisibleInkDustNode
|
||||
|
||||
private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let giftCode: PremiumGiftCodeInfo
|
||||
let subject: PremiumGiftCodeScreen.Subject
|
||||
let action: () -> Void
|
||||
let cancel: (Bool) -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openMessage: (EngineMessage.Id) -> Void
|
||||
let copyLink: (String) -> Void
|
||||
let shareLink: (String) -> Void
|
||||
let displayHiddenTooltip: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
giftCode: PremiumGiftCodeInfo,
|
||||
subject: PremiumGiftCodeScreen.Subject,
|
||||
action: @escaping () -> Void,
|
||||
cancel: @escaping (Bool) -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
copyLink: @escaping (String) -> Void,
|
||||
shareLink: @escaping (String) -> Void
|
||||
shareLink: @escaping (String) -> Void,
|
||||
displayHiddenTooltip: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.giftCode = giftCode
|
||||
self.subject = subject
|
||||
self.action = action
|
||||
self.cancel = cancel
|
||||
self.openPeer = openPeer
|
||||
self.openMessage = openMessage
|
||||
self.copyLink = copyLink
|
||||
self.shareLink = shareLink
|
||||
self.displayHiddenTooltip = displayHiddenTooltip
|
||||
}
|
||||
|
||||
static func ==(lhs: PremiumGiftCodeSheetContent, rhs: PremiumGiftCodeSheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.giftCode != rhs.giftCode {
|
||||
if lhs.subject != rhs.subject {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -72,15 +76,25 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
|
||||
init(context: AccountContext, giftCode: PremiumGiftCodeInfo) {
|
||||
var inProgress = false
|
||||
|
||||
init(context: AccountContext, subject: PremiumGiftCodeScreen.Subject) {
|
||||
self.context = context
|
||||
|
||||
super.init()
|
||||
|
||||
var peerIds: [EnginePeer.Id] = []
|
||||
peerIds.append(giftCode.fromPeerId)
|
||||
if let toPeerId = giftCode.toPeerId {
|
||||
peerIds.append(toPeerId)
|
||||
switch subject {
|
||||
case let .giftCode(giftCode):
|
||||
peerIds.append(giftCode.fromPeerId)
|
||||
if let toPeerId = giftCode.toPeerId {
|
||||
peerIds.append(toPeerId)
|
||||
}
|
||||
case let .boost(channelId, boost):
|
||||
peerIds.append(channelId)
|
||||
if let peerId = boost.peer?.id {
|
||||
peerIds.append(peerId)
|
||||
}
|
||||
}
|
||||
|
||||
self.disposable = (context.engine.data.get(
|
||||
@ -111,7 +125,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, giftCode: self.giftCode)
|
||||
return State(context: self.context, subject: self.subject)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
@ -133,7 +147,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
let accountContext = context.component.context
|
||||
|
||||
let state = context.state
|
||||
let giftCode = component.giftCode
|
||||
let subject = component.subject
|
||||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
let textSideInset: CGFloat = 32.0 + environment.safeInsets.left
|
||||
@ -161,17 +175,64 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
let descriptionText: String
|
||||
let additionalText: String
|
||||
let buttonText: String
|
||||
if let usedDate = giftCode.usedDate {
|
||||
let dateString = stringForMediumDate(timestamp: usedDate, strings: strings, dateTimeFormat: dateTimeFormat)
|
||||
titleText = "Used Gift Link"
|
||||
descriptionText = "This link was used to activate a **Telegram Premium** subscription."
|
||||
additionalText = "This link was used on \(dateString)."
|
||||
|
||||
let link: String?
|
||||
let date: Int32
|
||||
let fromPeer: EnginePeer?
|
||||
var toPeerId: EnginePeer.Id?
|
||||
let toPeer: EnginePeer?
|
||||
let months: Int32
|
||||
|
||||
var gloss = false
|
||||
switch subject {
|
||||
case let .giftCode(giftCode):
|
||||
gloss = !giftCode.isUsed
|
||||
if let usedDate = giftCode.usedDate {
|
||||
let dateString = stringForMediumDate(timestamp: usedDate, strings: strings, dateTimeFormat: dateTimeFormat)
|
||||
titleText = strings.GiftLink_UsedTitle
|
||||
descriptionText = strings.GiftLink_UsedDescription
|
||||
additionalText = strings.GiftLink_UsedFooter(dateString).string
|
||||
buttonText = strings.Common_OK
|
||||
} else {
|
||||
titleText = strings.GiftLink_Title
|
||||
descriptionText = strings.GiftLink_Description
|
||||
additionalText = strings.GiftLink_Footer
|
||||
buttonText = strings.GiftLink_UseLink
|
||||
}
|
||||
link = "https://t.me/giftcode/\(giftCode.slug)"
|
||||
date = giftCode.date
|
||||
fromPeer = state.peerMap[giftCode.fromPeerId]
|
||||
toPeerId = giftCode.toPeerId
|
||||
if let toPeerId = giftCode.toPeerId {
|
||||
toPeer = state.peerMap[toPeerId]
|
||||
} else {
|
||||
toPeer = nil
|
||||
}
|
||||
months = giftCode.months
|
||||
case let .boost(channelId, boost):
|
||||
titleText = strings.GiftLink_Title
|
||||
if let peer = boost.peer, !boost.flags.contains(.isUnclaimed) {
|
||||
toPeer = boost.peer
|
||||
if boost.slug == nil {
|
||||
descriptionText = strings.GiftLink_PersonalDescription(peer.compactDisplayTitle).string
|
||||
} else {
|
||||
descriptionText = strings.GiftLink_PersonalUsedDescription(peer.compactDisplayTitle).string
|
||||
}
|
||||
} else {
|
||||
toPeer = nil
|
||||
descriptionText = strings.GiftLink_UnclaimedDescription
|
||||
}
|
||||
if boost.slug == nil {
|
||||
additionalText = strings.GiftLink_NotUsedFooter
|
||||
} else {
|
||||
additionalText = ""
|
||||
}
|
||||
buttonText = strings.Common_OK
|
||||
} else {
|
||||
titleText = "Gift Link"
|
||||
descriptionText = "This link allows you to activate a **Telegram Premium** subscription."
|
||||
additionalText = "You can also [send this link]() to a friend as a gift."
|
||||
buttonText = "Use Link"
|
||||
link = nil
|
||||
date = boost.date
|
||||
toPeerId = boost.peer?.id
|
||||
fromPeer = state.peerMap[channelId]
|
||||
months = Int32(round(Float(boost.expires - boost.date) / (86400.0 * 30.0)))
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
@ -213,14 +274,17 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let link = "https://t.me/giftcode/\(giftCode.slug)"
|
||||
let linkButton = linkButton.update(
|
||||
component: Button(
|
||||
content: AnyComponent(
|
||||
LinkButtonContentComponent(theme: environment.theme, text: link)
|
||||
),
|
||||
action: {
|
||||
component.copyLink(link)
|
||||
if let link {
|
||||
component.copyLink(link)
|
||||
} else {
|
||||
component.displayHiddenTooltip()
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
@ -231,11 +295,10 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
let tableTextColor = theme.list.itemPrimaryTextColor
|
||||
let tableLinkColor = theme.list.itemAccentColor
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
|
||||
let fromPeer = state.peerMap[giftCode.fromPeerId]
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "from",
|
||||
title: "From",
|
||||
title: strings.GiftLink_From,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
|
||||
@ -250,17 +313,16 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
)
|
||||
)
|
||||
))
|
||||
if let toPeerId = giftCode.toPeerId {
|
||||
let toPeer = state.peerMap[toPeerId]
|
||||
if let toPeer {
|
||||
tableItems.append(.init(
|
||||
id: "to",
|
||||
title: "To",
|
||||
title: strings.GiftLink_To,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
|
||||
action: {
|
||||
if let peer = toPeer, peer.id != accountContext.account.peerId {
|
||||
component.openPeer(peer)
|
||||
if toPeer.id != accountContext.account.peerId {
|
||||
component.openPeer(toPeer)
|
||||
Queue.mainQueue().after(1.0, {
|
||||
component.cancel(false)
|
||||
})
|
||||
@ -269,58 +331,55 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
)
|
||||
)
|
||||
))
|
||||
} else if giftCode.isGiveaway {
|
||||
} else if toPeerId == nil {
|
||||
tableItems.append(.init(
|
||||
id: "to",
|
||||
title: "To",
|
||||
title: strings.GiftLink_To,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "No recipient", font: tableFont, textColor: tableTextColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.GiftLink_NoRecipient, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
}
|
||||
let giftTitle: String
|
||||
if giftCode.months == 12 {
|
||||
giftTitle = "Telegram Premium for 1 year"
|
||||
} else {
|
||||
giftTitle = "Telegram Premium for \(giftCode.months) months"
|
||||
}
|
||||
let giftTitle = strings.GiftLink_TelegramPremium(months)
|
||||
tableItems.append(.init(
|
||||
id: "gift",
|
||||
title: "Gift",
|
||||
title: strings.GiftLink_Gift,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: giftTitle, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
|
||||
let giftReason: String
|
||||
if giftCode.toPeerId == nil {
|
||||
giftReason = "Incomplete Giveaway"
|
||||
} else {
|
||||
giftReason = giftCode.isGiveaway ? "Giveaway" : "You were selected by the channel"
|
||||
if case let .giftCode(giftCode) = component.subject {
|
||||
let giftReason: String
|
||||
if giftCode.toPeerId == nil {
|
||||
giftReason = strings.GiftLink_Reason_Unclaimed
|
||||
} else {
|
||||
giftReason = giftCode.isGiveaway ? strings.GiftLink_Reason_Giveaway : strings.GiftLink_Reason_Gift
|
||||
}
|
||||
tableItems.append(.init(
|
||||
id: "reason",
|
||||
title: strings.GiftLink_Reason,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: giftReason, font: tableFont, textColor: giftCode.messageId != nil ? tableLinkColor : tableTextColor)))),
|
||||
isEnabled: true,
|
||||
action: {
|
||||
if let messageId = giftCode.messageId {
|
||||
component.openMessage(messageId)
|
||||
}
|
||||
Queue.mainQueue().after(1.0) {
|
||||
component.cancel(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
tableItems.append(.init(
|
||||
id: "reason",
|
||||
title: "Reason",
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: giftReason, font: tableFont, textColor: giftCode.messageId != nil ? tableLinkColor : tableTextColor)))),
|
||||
isEnabled: true,
|
||||
action: {
|
||||
if let messageId = giftCode.messageId {
|
||||
component.openMessage(messageId)
|
||||
}
|
||||
Queue.mainQueue().after(1.0) {
|
||||
component.cancel(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
tableItems.append(.init(
|
||||
id: "date",
|
||||
title: "Date",
|
||||
title: strings.GiftLink_Date,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: giftCode.date, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
|
||||
@ -348,7 +407,9 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
}
|
||||
},
|
||||
tapAction: { attributes, _ in
|
||||
component.shareLink("https://t.me/giftcode/\(giftCode.slug)")
|
||||
if let link {
|
||||
component.shareLink(link)
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
@ -363,15 +424,20 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: !giftCode.isUsed,
|
||||
gloss: gloss,
|
||||
iconName: nil,
|
||||
animationName: nil,
|
||||
iconPosition: .left,
|
||||
action: {
|
||||
if giftCode.isUsed {
|
||||
component.cancel(true)
|
||||
} else {
|
||||
isLoading: state.inProgress,
|
||||
action: { [weak state] in
|
||||
if gloss {
|
||||
component.action()
|
||||
if let state {
|
||||
state.inProgress = true
|
||||
state.updated()
|
||||
}
|
||||
} else {
|
||||
component.cancel(true)
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -430,36 +496,39 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let giftCode: PremiumGiftCodeInfo
|
||||
let subject: PremiumGiftCodeScreen.Subject
|
||||
let action: () -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openMessage: (EngineMessage.Id) -> Void
|
||||
let copyLink: (String) -> Void
|
||||
let shareLink: (String) -> Void
|
||||
let displayHiddenTooltip: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
giftCode: PremiumGiftCodeInfo,
|
||||
subject: PremiumGiftCodeScreen.Subject,
|
||||
action: @escaping () -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
copyLink: @escaping (String) -> Void,
|
||||
shareLink: @escaping (String) -> Void
|
||||
shareLink: @escaping (String) -> Void,
|
||||
displayHiddenTooltip: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.giftCode = giftCode
|
||||
self.subject = subject
|
||||
self.action = action
|
||||
self.openPeer = openPeer
|
||||
self.openMessage = openMessage
|
||||
self.copyLink = copyLink
|
||||
self.shareLink = shareLink
|
||||
self.displayHiddenTooltip = displayHiddenTooltip
|
||||
}
|
||||
|
||||
static func ==(lhs: PremiumGiftCodeSheetComponent, rhs: PremiumGiftCodeSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.giftCode != rhs.giftCode {
|
||||
if lhs.subject != rhs.subject {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -477,7 +546,7 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(PremiumGiftCodeSheetContent(
|
||||
context: context.component.context,
|
||||
giftCode: context.component.giftCode,
|
||||
subject: context.component.subject,
|
||||
action: context.component.action,
|
||||
cancel: { animate in
|
||||
if animate {
|
||||
@ -493,9 +562,11 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
openPeer: context.component.openPeer,
|
||||
openMessage: context.component.openMessage,
|
||||
copyLink: context.component.copyLink,
|
||||
shareLink: context.component.shareLink
|
||||
shareLink: context.component.shareLink,
|
||||
displayHiddenTooltip: context.component.displayHiddenTooltip
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
@ -534,6 +605,11 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
|
||||
public enum Subject: Equatable {
|
||||
case giftCode(PremiumGiftCodeInfo)
|
||||
case boost(EnginePeer.Id, ChannelBoostersContext.State.Boost)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
public var disposed: () -> Void = {}
|
||||
|
||||
@ -541,7 +617,7 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
giftCode: PremiumGiftCodeInfo,
|
||||
subject: PremiumGiftCodeScreen.Subject,
|
||||
forceDark: Bool = false,
|
||||
action: @escaping () -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void = { _ in },
|
||||
@ -551,9 +627,27 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
|
||||
self.context = context
|
||||
|
||||
var copyLinkImpl: ((String) -> Void)?
|
||||
super.init(context: context, component: PremiumGiftCodeSheetComponent(context: context, giftCode: giftCode, action: action, openPeer: openPeer, openMessage: openMessage, copyLink: { link in
|
||||
copyLinkImpl?(link)
|
||||
}, shareLink: shareLink), navigationBarAppearance: .none, statusBarStyle: .ignore, theme: forceDark ? .dark : .default)
|
||||
var displayHiddenTooltipImpl: (() -> Void)?
|
||||
super.init(
|
||||
context: context,
|
||||
component: PremiumGiftCodeSheetComponent(
|
||||
context: context,
|
||||
subject: subject,
|
||||
action: action,
|
||||
openPeer: openPeer,
|
||||
openMessage: openMessage,
|
||||
copyLink: { link in
|
||||
copyLinkImpl?(link)
|
||||
},
|
||||
shareLink: shareLink,
|
||||
displayHiddenTooltip: {
|
||||
displayHiddenTooltipImpl?()
|
||||
}
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: forceDark ? .dark : .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
|
||||
@ -563,9 +657,21 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, position: .top, action: { _ in return true }), in: .window(.root))
|
||||
}
|
||||
|
||||
displayHiddenTooltipImpl = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.GiftLink_LinkHidden, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return true }), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -581,15 +687,40 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.dismissAllTooltips()
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func dismissAllTooltips() {
|
||||
self.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismiss()
|
||||
}
|
||||
})
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismiss()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private final class LinkButtonContentComponent: CombinedComponent {
|
||||
let theme: PresentationTheme
|
||||
let text: String
|
||||
let text: String?
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
text: String
|
||||
text: String?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.text = text
|
||||
@ -609,6 +740,7 @@ private final class LinkButtonContentComponent: CombinedComponent {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
let dust = Child(DustComponent.self)
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
@ -621,36 +753,48 @@ private final class LinkButtonContentComponent: CombinedComponent {
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.text.replacingOccurrences(of: "https://", with: ""),
|
||||
font: Font.regular(17.0),
|
||||
textColor: component.theme.list.itemPrimaryTextColor,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset - sideInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: component.theme.list.itemAccentColor),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(background
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
context.add(text
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width - icon.size.width / 2.0 - 14.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
if let _ = component.text {
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: (component.text ?? "").replacingOccurrences(of: "https://", with: ""),
|
||||
font: Font.regular(17.0),
|
||||
textColor: component.theme.list.itemPrimaryTextColor,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset - sideInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: component.theme.list.itemAccentColor),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width - icon.size.width / 2.0 - 14.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
context.add(text
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
} else {
|
||||
let dust = dust.update(
|
||||
component: DustComponent(color: component.theme.list.itemSecondaryTextColor),
|
||||
availableSize: CGSize(width: context.availableSize.width * 0.8, height: context.availableSize.height * 0.54),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(dust
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
@ -955,3 +1099,53 @@ private final class PeerCellComponent: Component {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class DustComponent: Component {
|
||||
let color: UIColor
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
}
|
||||
|
||||
static func ==(lhs: DustComponent, rhs: DustComponent) -> Bool {
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let dustView = InvisibleInkDustView(textNode: nil, enableAnimations: true)
|
||||
|
||||
private var component: DustComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dustView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: DustComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let rects: [CGRect] = [CGRect(origin: .zero, size: availableSize).insetBy(dx: 5.0, dy: 5.0)]
|
||||
self.dustView.update(size: availableSize, color: component.color, textColor: component.color, rects: rects, wordRects: rects)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1522,10 +1522,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var newPerks: [String] = []
|
||||
if !dismissedPremiumAppIconsBadge {
|
||||
newPerks.append(PremiumPerk.appIcons.identifier)
|
||||
}
|
||||
let newPerks: [String] = []
|
||||
self.newPerks = newPerks
|
||||
self.updated()
|
||||
})
|
||||
@ -1856,7 +1853,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumAppIconsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
// let _ = ApplicationSpecificNotice.setDismissedPremiumAppIconsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
case .emojiStatus:
|
||||
@ -2918,8 +2915,8 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
completionImpl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds))
|
||||
if let self {
|
||||
self.animateSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2933,6 +2930,10 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
self.wasDismissed?()
|
||||
}
|
||||
|
||||
public func animateSuccess() {
|
||||
self.view.addSubview(ConfettiView(frame: self.view.bounds))
|
||||
}
|
||||
|
||||
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
|
||||
@ -19,8 +19,6 @@ import PeerListItemComponent
|
||||
import TelegramStringFormatting
|
||||
import AvatarNode
|
||||
|
||||
//TODO:localize
|
||||
|
||||
private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
@ -104,7 +102,6 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
// let closeButton = Child(Button.self)
|
||||
let header = Child(ReplaceBoostHeaderComponent.self)
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
let boostsBackground = Child(RoundedRectangle.self)
|
||||
@ -117,7 +114,6 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
|
||||
// let topInset: CGFloat = environment.navigationHeight + 22.0
|
||||
let textSideInset: CGFloat = 32.0
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
@ -168,8 +164,13 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let channelName = state.peer?.compactDisplayTitle ?? ""
|
||||
let descriptionString = "To boost **\(channelName)**, reassign a previous boost or gift **Telegram Premium** to a friend to get **3** additional boosts."
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.component.context.currentAppConfiguration.with({ $0 }))
|
||||
|
||||
var channelName = state.peer?.compactDisplayTitle ?? ""
|
||||
if channelName.count > 48 {
|
||||
channelName = "\(channelName.prefix(48))..."
|
||||
}
|
||||
let descriptionString = strings.ReassignBoost_Description(channelName, "\(premiumConfiguration.boostsPerGiftCount)").string
|
||||
|
||||
let description = description.update(
|
||||
component: MultilineTextComponent(
|
||||
@ -201,11 +202,11 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
if let cooldownUntil = boost.cooldownUntil, cooldownUntil > state.currentTime {
|
||||
let duration = cooldownUntil - state.currentTime
|
||||
let durationValue = stringForDuration(duration, position: nil)
|
||||
subtitle = "Available in \(durationValue)"
|
||||
subtitle = strings.ReassignBoost_AvailableIn(durationValue).string
|
||||
isEnabled = false
|
||||
} else {
|
||||
let expiresValue = stringForDate(timestamp: boost.expires, strings: strings)
|
||||
subtitle = "Boost expires on \(expiresValue)"
|
||||
subtitle = strings.ReassignBoost_ExpiresOn(expiresValue).string
|
||||
}
|
||||
|
||||
let accountContext = context.component.context
|
||||
@ -242,7 +243,8 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
selectedSlotsUpdated(state.selectedSlots)
|
||||
} else {
|
||||
let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Wait until the boost is available or get **3** more boosts by gifting a **Telegram Premium** subscription.", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return true })
|
||||
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: strings.ReassignBoost_WaitForCooldown("\(premiumConfiguration.boostsPerGiftCount)").string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return true })
|
||||
presentController(undoController)
|
||||
}
|
||||
})
|
||||
@ -254,7 +256,7 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
let boosts = boosts.update(
|
||||
component: List(boostItems),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0),
|
||||
availableSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
@ -272,9 +274,7 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
||||
.position(CGPoint(x: availableSize.width / 2.0, y: 226 + boosts.size.height / 2.0))
|
||||
)
|
||||
|
||||
let contentSize = CGSize(width: availableSize.width, height: 226.0 + boosts.size.height)
|
||||
|
||||
return contentSize
|
||||
return CGSize(width: availableSize.width, height: 226.0 + boosts.size.height + environment.safeInsets.bottom + 91.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -293,6 +293,8 @@ public class ReplaceBoostScreen: ViewController {
|
||||
let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
|
||||
|
||||
private let footerView: FooterView
|
||||
private var footerHeight: CGFloat = 0.0
|
||||
private var bottomOffset: CGFloat = 1000.0
|
||||
|
||||
private(set) var isExpanded = false
|
||||
private var panGestureRecognizer: UIPanGestureRecognizer?
|
||||
@ -350,6 +352,7 @@ public class ReplaceBoostScreen: ViewController {
|
||||
}
|
||||
self.controller?.replaceBoosts?(self.selectedSlots)
|
||||
}
|
||||
self.footerView.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -382,9 +385,22 @@ public class ReplaceBoostScreen: ViewController {
|
||||
return true
|
||||
}
|
||||
|
||||
private func updateFooterAlpha() {
|
||||
guard let (layout, _) = self.currentLayout else {
|
||||
return
|
||||
}
|
||||
let contentFrame = self.scrollView.convert(self.hostView.frame, to: self.view)
|
||||
let bottomOffset = contentFrame.maxY - layout.size.height
|
||||
|
||||
let backgroundAlpha: CGFloat = min(30.0, max(0.0, bottomOffset)) / 30.0
|
||||
self.footerView.updateBackgroundAlpha(backgroundAlpha, transition: .immediate)
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
let contentOffset = self.scrollView.contentOffset.y
|
||||
self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate)
|
||||
|
||||
self.updateFooterAlpha()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
@ -429,6 +445,7 @@ public class ReplaceBoostScreen: ViewController {
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) {
|
||||
let hadLayout = self.currentLayout != nil
|
||||
self.currentLayout = (layout, navigationHeight)
|
||||
|
||||
if let controller = self.controller, let navigationBar = controller.navigationBar, navigationBar.view.superview !== self.wrappingView {
|
||||
@ -541,9 +558,11 @@ public class ReplaceBoostScreen: ViewController {
|
||||
let footerInsets = UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right)
|
||||
|
||||
transition.setFrame(view: self.footerView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topInset), size: layout.size))
|
||||
let _ = self.footerView.update(size: layout.size, insets: footerInsets, theme: self.presentationData.theme, count: Int32(self.selectedSlots.count))
|
||||
self.footerHeight = self.footerView.update(size: layout.size, insets: footerInsets, theme: self.presentationData.theme, strings: self.presentationData.strings, count: Int32(self.selectedSlots.count))
|
||||
|
||||
self.footerView.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
if !hadLayout {
|
||||
self.updateFooterAlpha()
|
||||
}
|
||||
}
|
||||
|
||||
private var didPlayAppearAnimation = false
|
||||
@ -574,7 +593,7 @@ public class ReplaceBoostScreen: ViewController {
|
||||
factor = 0.15
|
||||
}
|
||||
if self.scrollView.contentSize.height > 0.0 && self.scrollView.contentSize.height < layout.size.height / 2.0 {
|
||||
return layout.size.height - self.scrollView.contentSize.height - layout.intrinsicInsets.bottom - 154.0
|
||||
return layout.size.height - self.scrollView.contentSize.height - layout.intrinsicInsets.bottom - 30.0
|
||||
} else {
|
||||
return floor(max(layout.size.width, layout.size.height) * factor)
|
||||
}
|
||||
@ -673,6 +692,8 @@ public class ReplaceBoostScreen: ViewController {
|
||||
self.bounds = bounds
|
||||
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
|
||||
self.updateFooterAlpha()
|
||||
case .ended:
|
||||
guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else {
|
||||
return
|
||||
@ -760,10 +781,14 @@ public class ReplaceBoostScreen: ViewController {
|
||||
self.bounds = bounds
|
||||
self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
}
|
||||
|
||||
self.updateFooterAlpha()
|
||||
case .cancelled:
|
||||
self.panGestureArguments = nil
|
||||
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut)))
|
||||
|
||||
self.updateFooterAlpha()
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -812,9 +837,9 @@ public class ReplaceBoostScreen: ViewController {
|
||||
presentControllerImpl?(c)
|
||||
}))
|
||||
|
||||
self.title = "Reassign Boosts"
|
||||
self.title = presentationData.strings.ReassignBoost_Title
|
||||
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
@ -890,6 +915,11 @@ public class ReplaceBoostScreen: ViewController {
|
||||
self.node.updateIsVisible(isVisible: true)
|
||||
}
|
||||
|
||||
override open func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.dismissAllTooltips()
|
||||
}
|
||||
|
||||
override open func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
@ -953,10 +983,10 @@ private final class FooterView: UIView {
|
||||
|
||||
fileprivate var inProgress = false
|
||||
|
||||
private var currentLayout: (CGSize, UIEdgeInsets, PresentationTheme, Int32)?
|
||||
func update(size: CGSize, insets: UIEdgeInsets, theme: PresentationTheme, count: Int32) -> CGFloat {
|
||||
private var currentLayout: (CGSize, UIEdgeInsets, PresentationTheme, PresentationStrings, Int32)?
|
||||
func update(size: CGSize, insets: UIEdgeInsets, theme: PresentationTheme, strings: PresentationStrings, count: Int32) -> CGFloat {
|
||||
let hadLayout = self.currentLayout != nil
|
||||
self.currentLayout = (size, insets, theme, count)
|
||||
self.currentLayout = (size, insets, theme, strings, count)
|
||||
|
||||
self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||
self.separatorView.backgroundColor = theme.rootController.tabBar.separatorColor
|
||||
@ -988,7 +1018,7 @@ private final class FooterView: UIView {
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: "Reassign Boosts",
|
||||
text: strings.ReassignBoost_ReassignBoosts,
|
||||
badge: Int(count),
|
||||
textColor: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: theme.list.itemCheckColors.foregroundColor,
|
||||
@ -1005,8 +1035,8 @@ private final class FooterView: UIView {
|
||||
return
|
||||
}
|
||||
self.inProgress = true
|
||||
if let (size, insets, theme, count) = self.currentLayout {
|
||||
let _ = self.update(size: size, insets: insets, theme: theme, count: count)
|
||||
if let (size, insets, theme, strings, count) = self.currentLayout {
|
||||
let _ = self.update(size: size, insets: insets, theme: theme, strings: strings, count: count)
|
||||
}
|
||||
self.action()
|
||||
}
|
||||
|
||||
@ -365,12 +365,6 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
||||
name = item.strings.Appearance_AppIconBlack
|
||||
case "PremiumTurbo":
|
||||
name = item.strings.Appearance_AppIconTurbo
|
||||
case "PremiumDuck":
|
||||
name = item.strings.Appearance_AppIconDuck
|
||||
case "PremiumCoffee":
|
||||
name = item.strings.Appearance_AppIconCoffee
|
||||
case "PremiumSteam":
|
||||
name = item.strings.Appearance_AppIconSteam
|
||||
default:
|
||||
name = icon.name
|
||||
}
|
||||
|
||||
@ -369,7 +369,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], accountPeer: EnginePeer?) -> [ThemeSettingsControllerEntry] {
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], accountPeer: EnginePeer?, nameColors: PeerNameColors) -> [ThemeSettingsControllerEntry] {
|
||||
var entries: [ThemeSettingsControllerEntry] = []
|
||||
|
||||
let strings = presentationData.strings
|
||||
@ -382,7 +382,8 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
||||
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
|
||||
|
||||
if let accountPeer {
|
||||
entries.append(.nameColor(presentationData.theme, strings.Appearance_NameColor, accountPeer.compactDisplayTitle, (accountPeer.nameColor ?? .blue).color))
|
||||
let colors = nameColors.get(accountPeer.nameColor ?? .blue)
|
||||
entries.append(.nameColor(presentationData.theme, strings.Appearance_NameColor, accountPeer.compactDisplayTitle, colors.main))
|
||||
}
|
||||
|
||||
entries.append(.autoNight(presentationData.theme, strings.Appearance_NightTheme, presentationThemeSettings.automaticThemeSwitchSetting.force, !presentationData.autoNightModeTriggered || presentationThemeSettings.automaticThemeSwitchSetting.force))
|
||||
@ -1064,7 +1065,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
chatThemes.insert(.builtin(.dayClassic), at: 0)
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ import ShareController
|
||||
import ItemListPeerActionItem
|
||||
import PremiumUI
|
||||
|
||||
private let maxUsersDisplayedLimit: Int32 = 50
|
||||
private let maxUsersDisplayedLimit: Int32 = 5
|
||||
|
||||
private final class ChannelStatsControllerArguments {
|
||||
let context: AccountContext
|
||||
@ -31,20 +31,20 @@ private final class ChannelStatsControllerArguments {
|
||||
let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> Void
|
||||
let copyBoostLink: (String) -> Void
|
||||
let shareBoostLink: (String) -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openBoost: (ChannelBoostersContext.State.Boost) -> Void
|
||||
let expandBoosters: () -> Void
|
||||
let openGifts: () -> Void
|
||||
let createPrepaidGiveaway: (PrepaidGiveaway) -> Void
|
||||
let updateGiftsSelected: (Bool) -> Void
|
||||
|
||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void) {
|
||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void) {
|
||||
self.context = context
|
||||
self.loadDetailedGraph = loadDetailedGraph
|
||||
self.openMessageStats = openMessage
|
||||
self.contextAction = contextAction
|
||||
self.copyBoostLink = copyBoostLink
|
||||
self.shareBoostLink = shareBoostLink
|
||||
self.openPeer = openPeer
|
||||
self.openBoost = openBoost
|
||||
self.expandBoosters = expandBoosters
|
||||
self.openGifts = openGifts
|
||||
self.createPrepaidGiveaway = createPrepaidGiveaway
|
||||
@ -118,7 +118,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case boostersTitle(PresentationTheme, String)
|
||||
case boostersPlaceholder(PresentationTheme, String)
|
||||
case boosterTabs(PresentationTheme, String, String, Bool)
|
||||
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer?, Int32, ChannelBoostersContext.State.Boost.Flags, Int32, Int32)
|
||||
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, ChannelBoostersContext.State.Boost)
|
||||
case boostersExpand(PresentationTheme, String)
|
||||
case boostersInfo(PresentationTheme, String)
|
||||
|
||||
@ -232,7 +232,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 2102
|
||||
case .boosterTabs:
|
||||
return 2103
|
||||
case let .booster(index, _, _, _, _, _, _, _):
|
||||
case let .booster(index, _, _, _):
|
||||
return 2104 + index
|
||||
case .boostersExpand:
|
||||
return 10000
|
||||
@ -439,8 +439,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsCount, lhsFlags, lhsDate, lhsExpires):
|
||||
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsCount, rhsFlags, rhsDate, rhsExpires) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsCount == rhsCount, lhsFlags == rhsFlags, lhsDate == rhsDate, lhsExpires == rhsExpires {
|
||||
case let .booster(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsBoost):
|
||||
if case let .booster(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsBoost) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsBoost == rhsBoost {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -548,39 +548,50 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return BoostsTabsItem(theme: presentationData.theme, boostsText: boostText, giftsText: giftText, selectedTab: giftSelected ? .gifts : .boosts, sectionId: self.section, selectionUpdated: { tab in
|
||||
arguments.updateGiftsSelected(tab == .gifts)
|
||||
})
|
||||
case let .booster(_, _, _, peer, count, flags, date, expires):
|
||||
let expiresValue = stringForDate(timestamp: expires, strings: presentationData.strings)
|
||||
case let .booster(_, _, _, boost):
|
||||
let count = boost.multiplier
|
||||
let expiresValue = stringForDate(timestamp: boost.expires, strings: presentationData.strings)
|
||||
let expiresString: String
|
||||
|
||||
let durationMonths = Int32(round(Float(expires - date) / (86400.0 * 30.0)))
|
||||
let durationString = "\(durationMonths)m"
|
||||
|
||||
let durationMonths = Int32(round(Float(boost.expires - boost.date) / (86400.0 * 30.0)))
|
||||
let durationString = presentationData.strings.Stats_Boosts_ShortMonth("\(durationMonths)").string
|
||||
|
||||
let title: String
|
||||
let icon: GiftOptionItem.Icon
|
||||
var label: String?
|
||||
if flags.contains(.isGiveaway) {
|
||||
label = "🏆 Giveaway"
|
||||
} else if flags.contains(.isGift) {
|
||||
label = "🎁 Gift"
|
||||
if boost.flags.contains(.isGiveaway) {
|
||||
label = "🏆 \(presentationData.strings.Stats_Boosts_Giveaway)"
|
||||
} else if boost.flags.contains(.isGift) {
|
||||
label = "🎁 \(presentationData.strings.Stats_Boosts_Gift)"
|
||||
}
|
||||
if let peer {
|
||||
|
||||
let color: GiftOptionItem.Icon.Color
|
||||
if durationMonths > 11 {
|
||||
color = .red
|
||||
} else if durationMonths > 5 {
|
||||
color = .blue
|
||||
} else {
|
||||
color = .green
|
||||
}
|
||||
|
||||
if boost.flags.contains(.isUnclaimed) {
|
||||
title = presentationData.strings.Stats_Boosts_Unclaimed
|
||||
icon = .image(color: color, name: "Premium/Unclaimed")
|
||||
expiresString = "\(durationString) • \(expiresValue)"
|
||||
} else if let peer = boost.peer {
|
||||
title = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
icon = .peer(peer)
|
||||
expiresString = presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string
|
||||
} else {
|
||||
let color: GiftOptionItem.Icon.Color
|
||||
if durationMonths > 11 {
|
||||
color = .red
|
||||
} else if durationMonths > 5 {
|
||||
color = .blue
|
||||
if let _ = label {
|
||||
expiresString = expiresValue
|
||||
} else {
|
||||
color = .green
|
||||
expiresString = presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string
|
||||
}
|
||||
if flags.contains(.isUnclaimed) {
|
||||
title = "Unclaimed"
|
||||
} else {
|
||||
if boost.flags.contains(.isUnclaimed) {
|
||||
title = presentationData.strings.Stats_Boosts_Unclaimed
|
||||
icon = .image(color: color, name: "Premium/Unclaimed")
|
||||
} else if flags.contains(.isGiveaway) {
|
||||
title = "To be distributed"
|
||||
} else if boost.flags.contains(.isGiveaway) {
|
||||
title = presentationData.strings.Stats_Boosts_ToBeDistributed
|
||||
icon = .image(color: color, name: "Premium/ToBeDistributed")
|
||||
} else {
|
||||
title = "Unknown"
|
||||
@ -588,9 +599,9 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
}
|
||||
expiresString = "\(durationString) • \(expiresValue)"
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: icon, title: title, titleFont: .bold, titleBadge: count > 1 ? "\(count)" : nil, subtitle: expiresString, label: label.flatMap { .semitransparent($0) }, sectionId: self.section, action: peer != nil && peer?.id != arguments.context.account.peerId ? {
|
||||
arguments.openPeer(peer!)
|
||||
} : nil)
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: icon, title: title, titleFont: .bold, titleBadge: count > 1 ? "\(count)" : nil, subtitle: expiresString, label: label.flatMap { .semitransparent($0) }, sectionId: self.section, action: {
|
||||
arguments.openBoost(boost)
|
||||
})
|
||||
case let .boostersExpand(theme, title):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandBoosters()
|
||||
@ -626,7 +637,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
default:
|
||||
color = .blue
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity)", subtitle: subtitle, label: nil, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, label: nil, sectionId: self.section, action: {
|
||||
arguments.createPrepaidGiveaway(prepaidGiveaway)
|
||||
})
|
||||
}
|
||||
@ -763,15 +774,14 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
entries.append(.boostOverviewTitle(presentationData.theme, presentationData.strings.Stats_Boosts_OverviewHeader))
|
||||
entries.append(.boostOverview(presentationData.theme, boostData))
|
||||
|
||||
//TODO:localize
|
||||
if !boostData.prepaidGiveaways.isEmpty {
|
||||
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
|
||||
entries.append(.boostPrepaidTitle(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysTitle))
|
||||
var i: Int32 = 0
|
||||
for giveaway in boostData.prepaidGiveaways {
|
||||
entries.append(.boostPrepaid(i, presentationData.theme, "\(giveaway.quantity) Telegram Premium", "\(giveaway.months)-month subscriptions", giveaway))
|
||||
entries.append(.boostPrepaid(i, presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawayCount(giveaway.quantity), presentationData.strings.Stats_Boosts_PrepaidGiveawayMonths("\(giveaway.months)").string, giveaway))
|
||||
i += 1
|
||||
}
|
||||
entries.append(.boostPrepaidInfo(presentationData.theme, "Select a giveaway you already paid for to set it up."))
|
||||
entries.append(.boostPrepaidInfo(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysInfo))
|
||||
}
|
||||
|
||||
let boostersTitle: String
|
||||
@ -802,7 +812,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
}
|
||||
|
||||
if boostsCount > 0 && giftsCount > 0 && boostsCount != giftsCount {
|
||||
entries.append(.boosterTabs(presentationData.theme, "\(boostsCount) Boosts", "\(giftsCount) Gifts", state.giftsSelected))
|
||||
entries.append(.boosterTabs(presentationData.theme, presentationData.strings.Stats_Boosts_TabBoosts(boostsCount), presentationData.strings.Stats_Boosts_TabGifts(giftsCount), state.giftsSelected))
|
||||
}
|
||||
|
||||
let selectedState: ChannelBoostersContext.State?
|
||||
@ -824,12 +834,12 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
}
|
||||
|
||||
for booster in boosters {
|
||||
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster.peer, booster.multiplier, booster.flags, booster.date, booster.expires))
|
||||
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster))
|
||||
boosterIndex += 1
|
||||
}
|
||||
|
||||
if !effectiveExpanded {
|
||||
entries.append(.boostersExpand(presentationData.theme, presentationData.strings.PeopleNearby_ShowMorePeople(Int32(selectedState.count) - maxUsersDisplayedLimit)))
|
||||
entries.append(.boostersExpand(presentationData.theme, presentationData.strings.Stats_Boosts_ShowMoreBoosts(Int32(selectedState.count) - maxUsersDisplayedLimit)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -842,8 +852,8 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
entries.append(.boostLinkInfo(presentationData.theme, presentationData.strings.Stats_Boosts_LinkInfo))
|
||||
|
||||
if giveawayAvailable {
|
||||
entries.append(.gifts(presentationData.theme, "Get Boosts via Gifts"))
|
||||
entries.append(.giftsInfo(presentationData.theme, "Get more boosts for your channel by gifting Premium to your subscribers."))
|
||||
entries.append(.gifts(presentationData.theme, presentationData.strings.Stats_Boosts_GetBoosts))
|
||||
entries.append(.giftsInfo(presentationData.theme, presentationData.strings.Stats_Boosts_GetBoostsInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -899,7 +909,6 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
|
||||
var presentImpl: ((ViewController) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var navigateToProfileImpl: ((EnginePeer) -> Void)?
|
||||
|
||||
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
|
||||
return statsContext.loadDetailedGraph(graph, x: x)
|
||||
@ -954,8 +963,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
}
|
||||
presentImpl?(shareController)
|
||||
},
|
||||
openPeer: { peer in
|
||||
navigateToProfileImpl?(peer)
|
||||
openBoost: { boost in
|
||||
let controller = PremiumGiftCodeScreen(context: context, subject: .boost(peerId, boost), action: {})
|
||||
pushImpl?(controller)
|
||||
},
|
||||
expandBoosters: {
|
||||
updateState { $0.withUpdatedBoostersExpanded(true) }
|
||||
@ -1087,11 +1097,6 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
navigateToProfileImpl = { [weak controller] peer in
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,14 @@ public struct MyBoostStatus: Equatable {
|
||||
public let date: Int32
|
||||
public let expires: Int32
|
||||
public let cooldownUntil: Int32?
|
||||
|
||||
public init(slot: Int32, peer: EnginePeer?, date: Int32, expires: Int32, cooldownUntil: Int32?) {
|
||||
self.slot = slot
|
||||
self.peer = peer
|
||||
self.date = date
|
||||
self.expires = expires
|
||||
self.cooldownUntil = cooldownUntil
|
||||
}
|
||||
}
|
||||
|
||||
public let boosts: [Boost]
|
||||
@ -175,7 +183,7 @@ private final class ChannelBoostersContextImpl {
|
||||
var result: [ChannelBoostersContext.State.Boost] = []
|
||||
for boost in cachedResult.boosts {
|
||||
let peer = boost.peerId.flatMap { transaction.getPeer($0) }
|
||||
result.append(ChannelBoostersContext.State.Boost(flags: ChannelBoostersContext.State.Boost.Flags(rawValue: boost.flags), id: boost.id, peer: peer.flatMap { EnginePeer($0) }, date: boost.date, expires: boost.expires, multiplier: boost.multiplier))
|
||||
result.append(ChannelBoostersContext.State.Boost(flags: ChannelBoostersContext.State.Boost.Flags(rawValue: boost.flags), id: boost.id, peer: peer.flatMap { EnginePeer($0) }, date: boost.date, expires: boost.expires, multiplier: boost.multiplier, slug: boost.slug))
|
||||
}
|
||||
return (result, cachedResult.count, true)
|
||||
} else {
|
||||
@ -211,7 +219,7 @@ private final class ChannelBoostersContextImpl {
|
||||
}
|
||||
|
||||
func loadMore() {
|
||||
if self.isLoadingMore {
|
||||
if self.isLoadingMore || !self.canLoadMore {
|
||||
return
|
||||
}
|
||||
self.isLoadingMore = true
|
||||
@ -256,7 +264,6 @@ private final class ChannelBoostersContextImpl {
|
||||
switch boost {
|
||||
case let .boost(flags, id, userId, giveawayMessageId, date, expires, usedGiftSlug, multiplier):
|
||||
let _ = giveawayMessageId
|
||||
let _ = usedGiftSlug
|
||||
var boostFlags: ChannelBoostersContext.State.Boost.Flags = []
|
||||
var boostPeer: EnginePeer?
|
||||
if let userId = userId {
|
||||
@ -274,7 +281,7 @@ private final class ChannelBoostersContextImpl {
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
boostFlags.insert(.isUnclaimed)
|
||||
}
|
||||
resultBoosts.append(ChannelBoostersContext.State.Boost(flags: boostFlags, id: id, peer: boostPeer, date: date, expires: expires, multiplier: multiplier ?? 1))
|
||||
resultBoosts.append(ChannelBoostersContext.State.Boost(flags: boostFlags, id: id, peer: boostPeer, date: date, expires: expires, multiplier: multiplier ?? 1, slug: usedGiftSlug))
|
||||
}
|
||||
}
|
||||
if populateCache {
|
||||
@ -305,11 +312,19 @@ private final class ChannelBoostersContextImpl {
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
strongSelf.hasLoadedOnce = true
|
||||
strongSelf.canLoadMore = !boosters.isEmpty
|
||||
strongSelf.canLoadMore = !boosters.isEmpty && nextOffset != nil
|
||||
if strongSelf.canLoadMore {
|
||||
strongSelf.count = max(updatedCount, Int32(strongSelf.results.count))
|
||||
var resultsCount: Int32 = 0
|
||||
for result in strongSelf.results {
|
||||
resultsCount += result.multiplier
|
||||
}
|
||||
strongSelf.count = max(updatedCount, resultsCount)
|
||||
} else {
|
||||
strongSelf.count = Int32(strongSelf.results.count)
|
||||
var resultsCount: Int32 = 0
|
||||
for result in strongSelf.results {
|
||||
resultsCount += result.multiplier
|
||||
}
|
||||
strongSelf.count = resultsCount
|
||||
}
|
||||
strongSelf.updateState()
|
||||
}))
|
||||
@ -357,6 +372,7 @@ public final class ChannelBoostersContext {
|
||||
public var date: Int32
|
||||
public var expires: Int32
|
||||
public var multiplier: Int32
|
||||
public var slug: String?
|
||||
}
|
||||
public var boosts: [Boost]
|
||||
public var isLoadingMore: Bool
|
||||
@ -418,6 +434,7 @@ private final class CachedChannelBoosters: Codable {
|
||||
case date
|
||||
case expires
|
||||
case multiplier
|
||||
case slug
|
||||
}
|
||||
|
||||
var flags: Int32
|
||||
@ -426,14 +443,16 @@ private final class CachedChannelBoosters: Codable {
|
||||
var date: Int32
|
||||
var expires: Int32
|
||||
var multiplier: Int32
|
||||
var slug: String?
|
||||
|
||||
init(flags: Int32, id: String, peerId: EnginePeer.Id?, date: Int32, expires: Int32, multiplier: Int32) {
|
||||
init(flags: Int32, id: String, peerId: EnginePeer.Id?, date: Int32, expires: Int32, multiplier: Int32, slug: String?) {
|
||||
self.flags = flags
|
||||
self.id = id
|
||||
self.peerId = peerId
|
||||
self.date = date
|
||||
self.expires = expires
|
||||
self.multiplier = multiplier
|
||||
self.slug = slug
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
@ -445,6 +464,7 @@ private final class CachedChannelBoosters: Codable {
|
||||
self.date = try container.decode(Int32.self, forKey: .date)
|
||||
self.expires = try container.decode(Int32.self, forKey: .expires)
|
||||
self.multiplier = try container.decode(Int32.self, forKey: .multiplier)
|
||||
self.slug = try container.decodeIfPresent(String.self, forKey: .slug)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
@ -456,6 +476,7 @@ private final class CachedChannelBoosters: Codable {
|
||||
try container.encode(self.date, forKey: .date)
|
||||
try container.encode(self.expires, forKey: .expires)
|
||||
try container.encode(self.multiplier, forKey: .multiplier)
|
||||
try container.encodeIfPresent(self.slug, forKey: .slug)
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,7 +490,7 @@ private final class CachedChannelBoosters: Codable {
|
||||
}
|
||||
|
||||
init(boosts: [ChannelBoostersContext.State.Boost], count: Int32) {
|
||||
self.boosts = boosts.map { CachedBoost(flags: $0.flags.rawValue, id: $0.id, peerId: $0.peer?.id, date: $0.date, expires: $0.expires, multiplier: $0.multiplier) }
|
||||
self.boosts = boosts.map { CachedBoost(flags: $0.flags.rawValue, id: $0.id, peerId: $0.peer?.id, date: $0.date, expires: $0.expires, multiplier: $0.multiplier, slug: $0.slug) }
|
||||
self.count = count
|
||||
}
|
||||
|
||||
|
||||
@ -114,7 +114,7 @@ public struct CachedPremiumGiftOption: Equatable, PostboxCoding {
|
||||
}
|
||||
}
|
||||
|
||||
public enum PeerNameColor: Int32, CaseIterable {
|
||||
public enum PeerNameColor: Equatable {
|
||||
case red
|
||||
case orange
|
||||
case violet
|
||||
@ -122,13 +122,49 @@ public enum PeerNameColor: Int32, CaseIterable {
|
||||
case cyan
|
||||
case blue
|
||||
case pink
|
||||
case redDash
|
||||
case orangeDash
|
||||
case violetDash
|
||||
case greenDash
|
||||
case cyanDash
|
||||
case blueDash
|
||||
case pinkDash
|
||||
case other(Int32)
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
switch rawValue {
|
||||
case 0:
|
||||
self = .red
|
||||
case 1:
|
||||
self = .orange
|
||||
case 2:
|
||||
self = .violet
|
||||
case 3:
|
||||
self = .green
|
||||
case 4:
|
||||
self = .cyan
|
||||
case 5:
|
||||
self = .blue
|
||||
case 6:
|
||||
self = .pink
|
||||
default:
|
||||
self = .other(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: Int32 {
|
||||
switch self {
|
||||
case .red:
|
||||
return 0
|
||||
case .orange:
|
||||
return 1
|
||||
case .violet:
|
||||
return 2
|
||||
case .green:
|
||||
return 3
|
||||
case .cyan:
|
||||
return 4
|
||||
case .blue:
|
||||
return 5
|
||||
case .pink:
|
||||
return 6
|
||||
case let .other(value):
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct PeerEmojiStatus: Equatable, Codable {
|
||||
|
||||
@ -348,12 +348,10 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||
action = .toggleForum(isForum: newValue == .boolTrue)
|
||||
case let .channelAdminLogEventActionToggleAntiSpam(newValue):
|
||||
action = .toggleAntiSpam(isEnabled: newValue == .boolTrue)
|
||||
default:
|
||||
action = .toggleInvites(false)
|
||||
// case let .channelAdminLogEventActionChangeColor(prevValue, newValue):
|
||||
// action = .changeNameColor(prev: PeerNameColor(rawValue: prevValue), new: PeerNameColor(rawValue: newValue))
|
||||
// case let .channelAdminLogEventActionChangeBackgroundEmoji(prevValue, newValue):
|
||||
// action = .changeBackgroundEmojiId(prev: prevValue, new: newValue)
|
||||
case let .channelAdminLogEventActionChangeColor(prevValue, newValue):
|
||||
action = .changeNameColor(prev: PeerNameColor(rawValue: prevValue), new: PeerNameColor(rawValue: newValue))
|
||||
case let .channelAdminLogEventActionChangeBackgroundEmoji(prevValue, newValue):
|
||||
action = .changeBackgroundEmojiId(prev: prevValue, new: newValue)
|
||||
}
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
if let action = action {
|
||||
|
||||
@ -223,27 +223,19 @@ public extension Peer {
|
||||
if let nameColor = user.nameColor {
|
||||
return nameColor
|
||||
} else {
|
||||
return PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)) ?? .blue
|
||||
return PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7))
|
||||
}
|
||||
case let channel as TelegramChannel:
|
||||
if let nameColor = channel.nameColor {
|
||||
return nameColor
|
||||
} else {
|
||||
return PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)) ?? .blue
|
||||
return PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7))
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var hasCustomNameColor: Bool {
|
||||
let defaultNameColor = PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)) ?? .blue
|
||||
if self.nameColor != defaultNameColor {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var backgroundEmojiId: Int64? {
|
||||
switch self {
|
||||
case let user as TelegramUser:
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import TelegramCore
|
||||
|
||||
public extension PeerNameColor {
|
||||
var color: UIColor {
|
||||
return self.dashColors.0
|
||||
}
|
||||
|
||||
var dashColors: (UIColor, UIColor?) {
|
||||
switch self {
|
||||
case .red:
|
||||
return (UIColor(rgb: 0xCC5049), nil)
|
||||
case .orange:
|
||||
return (UIColor(rgb: 0xD67722), nil)
|
||||
case .violet:
|
||||
return (UIColor(rgb: 0x955CDB), nil)
|
||||
case .green:
|
||||
return (UIColor(rgb: 0x40A920), nil)
|
||||
case .cyan:
|
||||
return (UIColor(rgb: 0x309EBA), nil)
|
||||
case .blue:
|
||||
return (UIColor(rgb: 0x368AD1), nil)
|
||||
case .pink:
|
||||
return (UIColor(rgb: 0xC7508B), nil)
|
||||
case .redDash:
|
||||
return (UIColor(rgb: 0xE15052), UIColor(rgb: 0xF9AE63))
|
||||
case .orangeDash:
|
||||
return (UIColor(rgb: 0xE0802B), UIColor(rgb: 0xFAC534))
|
||||
case .violetDash:
|
||||
return (UIColor(rgb: 0xA05FF3), UIColor(rgb: 0xF48FFF))
|
||||
case .greenDash:
|
||||
return (UIColor(rgb: 0x27A910), UIColor(rgb: 0xA7DC57))
|
||||
case .cyanDash:
|
||||
return (UIColor(rgb: 0x27ACCE), UIColor(rgb: 0x82E8D6))
|
||||
case .blueDash:
|
||||
return (UIColor(rgb: 0x3391D4), UIColor(rgb: 0x7DD3F0))
|
||||
case .pinkDash:
|
||||
return (UIColor(rgb: 0xdd4371), UIColor(rgb: 0xffbe9f))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,18 +259,23 @@ private final class ChatInputTextContainer: NSTextContainer {
|
||||
public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDelegate, NSTextStorageDelegate {
|
||||
public final class Theme: Equatable {
|
||||
public final class Quote: Equatable {
|
||||
public enum LineStyle {
|
||||
case solid
|
||||
case doubleDashed
|
||||
case tripleDashed
|
||||
}
|
||||
public let background: UIColor
|
||||
public let foreground: UIColor
|
||||
public let isDashed: Bool
|
||||
public let lineStyle: LineStyle
|
||||
|
||||
public init(
|
||||
background: UIColor,
|
||||
foreground: UIColor,
|
||||
isDashed: Bool
|
||||
lineStyle: LineStyle
|
||||
) {
|
||||
self.background = background
|
||||
self.foreground = foreground
|
||||
self.isDashed = isDashed
|
||||
self.lineStyle = lineStyle
|
||||
}
|
||||
|
||||
public static func ==(lhs: Quote, rhs: Quote) -> Bool {
|
||||
@ -280,7 +285,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
|
||||
if !lhs.foreground.isEqual(rhs.foreground) {
|
||||
return false
|
||||
}
|
||||
if lhs.isDashed != rhs.isDashed {
|
||||
if lhs.lineStyle != rhs.lineStyle {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -763,7 +768,8 @@ private final class QuoteBackgroundView: UIView {
|
||||
self.backgroundView.update(
|
||||
size: size,
|
||||
primaryColor: theme.foreground,
|
||||
secondaryColor: theme.isDashed ? .clear : nil,
|
||||
secondaryColor: theme.lineStyle != .solid ? .clear : nil,
|
||||
thirdColor: theme.lineStyle == .tripleDashed ? .clear : nil,
|
||||
pattern: nil,
|
||||
animation: .None
|
||||
)
|
||||
|
||||
@ -1213,7 +1213,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
let availableWidth = max(60.0, availableContentWidth + 6.0)
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
}
|
||||
|
||||
if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil {
|
||||
|
||||
@ -177,17 +177,24 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
|
||||
let author = message.author
|
||||
let nameColors = author?.nameColor.flatMap { context.peerNameColors.get($0) }
|
||||
|
||||
let mainColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
var tertiaryColor: UIColor?
|
||||
if !incoming {
|
||||
mainColor = messageTheme.accentTextColor
|
||||
if let _ = author?.nameColor?.dashColors.1 {
|
||||
if let _ = nameColors?.secondary {
|
||||
secondaryColor = .clear
|
||||
}
|
||||
if let _ = nameColors?.tertiary {
|
||||
tertiaryColor = .clear
|
||||
}
|
||||
} else {
|
||||
var authorNameColor: UIColor?
|
||||
authorNameColor = author?.nameColor?.color
|
||||
secondaryColor = author?.nameColor?.dashColors.1
|
||||
authorNameColor = nameColors?.main
|
||||
secondaryColor = nameColors?.secondary
|
||||
tertiaryColor = nameColors?.tertiary
|
||||
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
@ -853,13 +860,13 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
if let current = self.backgroundView {
|
||||
backgroundView = current
|
||||
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
|
||||
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, pattern: nil, animation: animation)
|
||||
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, pattern: nil, animation: animation)
|
||||
} else {
|
||||
backgroundView = MessageInlineBlockBackgroundView()
|
||||
self.backgroundView = backgroundView
|
||||
backgroundView.frame = backgroundFrame
|
||||
self.transformContainer.view.insertSubview(backgroundView, at: 0)
|
||||
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, pattern: nil, animation: .None)
|
||||
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, pattern: nil, animation: .None)
|
||||
}
|
||||
} else {
|
||||
if let backgroundView = self.backgroundView {
|
||||
|
||||
@ -1270,7 +1270,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
|
||||
reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)),
|
||||
@ -1881,31 +1881,40 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil {
|
||||
authorNameColor = (peer as Peer).nameColor?.color
|
||||
let peer = (peer as Peer)
|
||||
let nameColors = peer.nameColor.flatMap { item.context.peerNameColors.get($0) }
|
||||
authorNameColor = nameColors?.main
|
||||
} else if let effectiveAuthor = effectiveAuthor {
|
||||
let nameColor: UIColor
|
||||
let nameColor = effectiveAuthor.nameColor ?? .blue
|
||||
let nameColors = item.context.peerNameColors.get(nameColor)
|
||||
let color: UIColor
|
||||
if incoming {
|
||||
nameColor = (effectiveAuthor.nameColor ?? .blue).color
|
||||
color = nameColors.main
|
||||
} else {
|
||||
nameColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
color = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
}
|
||||
authorNameColor = nameColor
|
||||
authorNameColor = color
|
||||
}
|
||||
|
||||
if initialDisplayHeader && displayAuthorInfo {
|
||||
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil {
|
||||
authorNameString = EnginePeer(peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
authorNameColor = (peer as Peer).nameColor?.color
|
||||
|
||||
let peer = (peer as Peer)
|
||||
let nameColors = peer.nameColor.flatMap { item.context.peerNameColors.get($0) }
|
||||
authorNameColor = nameColors?.main
|
||||
} else if let effectiveAuthor = effectiveAuthor {
|
||||
authorNameString = EnginePeer(effectiveAuthor).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
|
||||
let nameColor: UIColor
|
||||
let nameColor = effectiveAuthor.nameColor ?? .blue
|
||||
let nameColors = item.context.peerNameColors.get(nameColor)
|
||||
let color: UIColor
|
||||
if incoming {
|
||||
nameColor = (effectiveAuthor.nameColor ?? .blue).color
|
||||
color = nameColors.main
|
||||
} else {
|
||||
nameColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
color = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
}
|
||||
authorNameColor = nameColor
|
||||
authorNameColor = color
|
||||
|
||||
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
|
||||
} else if effectiveAuthor.isScam {
|
||||
@ -1913,11 +1922,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
} else if effectiveAuthor.isFake {
|
||||
currentCredibilityIcon = .text(color: incoming ? item.presentationData.theme.theme.chat.message.incoming.scamColor : item.presentationData.theme.theme.chat.message.outgoing.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
|
||||
} else if let user = effectiveAuthor as? TelegramUser, let emojiStatus = user.emojiStatus {
|
||||
currentCredibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor, themeColor: nameColor.withMultipliedAlpha(0.4), loopMode: .count(2))
|
||||
currentCredibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor, themeColor: color.withMultipliedAlpha(0.4), loopMode: .count(2))
|
||||
} else if effectiveAuthor.isVerified {
|
||||
currentCredibilityIcon = .verified(fillColor: item.presentationData.theme.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
|
||||
} else if effectiveAuthor.isPremium {
|
||||
currentCredibilityIcon = .premium(color: nameColor.withMultipliedAlpha(0.4))
|
||||
currentCredibilityIcon = .premium(color: color.withMultipliedAlpha(0.4))
|
||||
}
|
||||
}
|
||||
if let rawAuthorNameColor = authorNameColor {
|
||||
@ -2178,7 +2187,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
forwardAuthorSignature = forwardInfo.authorSignature
|
||||
}
|
||||
}
|
||||
let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
let sizeAndApply = forwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
forwardInfoSizeApply = (sizeAndApply.0, { width in sizeAndApply.1(width) })
|
||||
|
||||
forwardInfoOriginY = headerSize.height
|
||||
@ -2205,7 +2214,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
}
|
||||
|
||||
let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, nil, nil, ChatMessageForwardInfoNode.StoryData(storyType: storyType), CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
let sizeAndApply = forwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, nil, nil, ChatMessageForwardInfoNode.StoryData(storyType: storyType), CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
forwardInfoSizeApply = (sizeAndApply.0, { width in sizeAndApply.1(width) })
|
||||
|
||||
if storyType != .regular {
|
||||
|
||||
@ -6,6 +6,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import LocalizedPeerData
|
||||
import AccountContext
|
||||
|
||||
public enum ChatMessageForwardInfoType: Equatable {
|
||||
case bubble(incoming: Bool)
|
||||
@ -106,10 +107,10 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public static func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ storyData: StoryData?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) {
|
||||
public static func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ storyData: StoryData?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) {
|
||||
let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode)
|
||||
|
||||
return { presentationData, strings, type, peer, authorName, psaType, storyData, constrainedSize in
|
||||
return { context, presentationData, strings, type, peer, authorName, psaType, storyData, constrainedSize in
|
||||
let fontSize = floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)
|
||||
let prefixFont = Font.regular(fontSize)
|
||||
let peerFont = Font.medium(fontSize)
|
||||
@ -163,8 +164,8 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
if incoming {
|
||||
if let color = peer?.nameColor?.color {
|
||||
titleColor = color
|
||||
if let nameColor = peer?.nameColor {
|
||||
titleColor = context.peerNameColors.get(nameColor).main
|
||||
} else {
|
||||
titleColor = presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
|
||||
@ -189,7 +189,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
@ -226,24 +225,24 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
textSpacing += 13.0
|
||||
|
||||
if unclaimed {
|
||||
title = "Unclaimed Prize"
|
||||
title = item.presentationData.strings.Notification_PremiumPrize_Unclaimed
|
||||
} else {
|
||||
title = "Congratulations!"
|
||||
title = item.presentationData.strings.Notification_PremiumPrize_Title
|
||||
}
|
||||
var peerName = ""
|
||||
if let channelId, let channel = item.message.peers[channelId] {
|
||||
peerName = EnginePeer(channel).compactDisplayTitle
|
||||
}
|
||||
if unclaimed {
|
||||
text = "You have an unclaimed prize from a giveaway by **\(peerName)**.\n\nThis prize is a **Telegram Premium** subscription for **\(monthsValue)** months."
|
||||
text = item.presentationData.strings.Notification_PremiumPrize_UnclaimedText(peerName, item.presentationData.strings.Notification_PremiumPrize_Months(monthsValue)).string
|
||||
} else if fromGiveaway {
|
||||
text = "You won a prize in a giveaway organized by **\(peerName)**.\n\nYour prize is a **Telegram Premium** subscription for **\(monthsValue)** months."
|
||||
text = item.presentationData.strings.Notification_PremiumPrize_GiveawayText(peerName, item.presentationData.strings.Notification_PremiumPrize_Months(monthsValue)).string
|
||||
} else {
|
||||
text = "You've received a gift from **\(peerName)**.\n\nYour gift is a **Telegram Premium** subscription for **\(monthsValue)** months."
|
||||
text = item.presentationData.strings.Notification_PremiumPrize_GiftText(peerName, item.presentationData.strings.Notification_PremiumPrize_Months(monthsValue)).string
|
||||
}
|
||||
|
||||
months = monthsValue
|
||||
buttonTitle = "Open Gift Link"
|
||||
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
|
||||
hasServiceMessage = false
|
||||
default:
|
||||
break
|
||||
|
||||
@ -222,11 +222,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: .white)
|
||||
|
||||
//TODO:localize
|
||||
let prizeTitleString = NSAttributedString(string: "Giveaway Prizes", font: titleFont, textColor: textColor)
|
||||
let prizeTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle, font: titleFont, textColor: textColor)
|
||||
var prizeTextString: NSAttributedString?
|
||||
if let giveaway {
|
||||
prizeTextString = parseMarkdownIntoAttributedString("**\(giveaway.quantity)** Telegram Premium Subscriptions for **\(giveaway.months)** months.", attributes: MarkdownAttributes(
|
||||
prizeTextString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_Giveaway_Message_PrizeText(
|
||||
item.presentationData.strings.Chat_Giveaway_Message_Subscriptions(giveaway.quantity),
|
||||
item.presentationData.strings.Chat_Giveaway_Message_Months(giveaway.months)
|
||||
).string, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
@ -236,22 +238,22 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
), textAlignment: .center)
|
||||
}
|
||||
|
||||
let participantsTitleString = NSAttributedString(string: "Participants", font: titleFont, textColor: textColor)
|
||||
let participantsTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_ParticipantsTitle, font: titleFont, textColor: textColor)
|
||||
let participantsText: String
|
||||
let countriesText: String
|
||||
|
||||
if let giveaway {
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participantsText = "All users who join the channels below after this date:"
|
||||
participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsNewMany
|
||||
} else {
|
||||
participantsText = "All users who join this channel after this date:"
|
||||
participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsNew
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participantsText = "All subscribers of the channels below:"
|
||||
participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsMany
|
||||
} else {
|
||||
participantsText = "All subscribers of this channel:"
|
||||
participantsText = item.presentationData.strings.Chat_Giveaway_Message_Participants
|
||||
}
|
||||
}
|
||||
if !giveaway.countries.isEmpty {
|
||||
@ -270,13 +272,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
for i in 0 ..< countryNames.count {
|
||||
countries.append(countryNames[i])
|
||||
if i == countryNames.count - 2 {
|
||||
countries.append(" and ")
|
||||
countries.append(item.presentationData.strings.Chat_Giveaway_Message_CountriesLastDelimiter)
|
||||
} else if i < countryNames.count - 2 {
|
||||
countries.append(", ")
|
||||
countries.append(item.presentationData.strings.Chat_Giveaway_Message_CountriesDelimiter)
|
||||
}
|
||||
}
|
||||
}
|
||||
countriesText = "from \(countries)"
|
||||
countriesText = item.presentationData.strings.Chat_Giveaway_Message_CountriesFrom(countries).string
|
||||
} else {
|
||||
countriesText = ""
|
||||
}
|
||||
@ -289,7 +291,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
let countriesTextString = NSAttributedString(string: countriesText, font: textFont, textColor: textColor)
|
||||
|
||||
let dateTitleString = NSAttributedString(string: "Winners Selection Date", font: titleFont, textColor: textColor)
|
||||
let dateTitleString = NSAttributedString(string: item.presentationData.strings.Chat_Giveaway_Message_DateTitle, font: titleFont, textColor: textColor)
|
||||
var dateTextString: NSAttributedString?
|
||||
if let giveaway {
|
||||
dateTextString = NSAttributedString(string: stringForFullDate(timestamp: giveaway.untilDate, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), font: textFont, textColor: textColor)
|
||||
@ -392,7 +394,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
}
|
||||
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, "LEARN MORE", titleColor, false, true)
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true)
|
||||
|
||||
let months = giveaway?.months ?? 0
|
||||
let animationName: String
|
||||
|
||||
@ -567,7 +567,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
||||
}
|
||||
}
|
||||
let availableWidth = max(60.0, availableContentWidth - normalDisplaySize.width + 6.0)
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
}
|
||||
|
||||
if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil {
|
||||
|
||||
@ -432,7 +432,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
let availableWidth: CGFloat = max(60.0, availableContentWidth - 210.0 + 6.0)
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
}
|
||||
|
||||
var notConsumed = false
|
||||
|
||||
@ -193,14 +193,18 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
let mainColor: UIColor
|
||||
let dustColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
var tertiaryColor: UIColor?
|
||||
|
||||
var authorNameColor: UIColor?
|
||||
var dashSecondaryColor: UIColor?
|
||||
var dashTertiaryColor: UIColor?
|
||||
|
||||
let author = arguments.message?.effectiveAuthor
|
||||
|
||||
authorNameColor = author?.nameColor?.color
|
||||
dashSecondaryColor = author?.nameColor?.dashColors.1
|
||||
let colors = author?.nameColor.flatMap { arguments.context.peerNameColors.get($0) }
|
||||
authorNameColor = colors?.main
|
||||
dashSecondaryColor = colors?.secondary
|
||||
dashTertiaryColor = colors?.tertiary
|
||||
|
||||
switch arguments.type {
|
||||
case let .bubble(incoming):
|
||||
@ -209,6 +213,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
secondaryColor = dashSecondaryColor
|
||||
tertiaryColor = dashTertiaryColor
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
@ -217,6 +222,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
if dashSecondaryColor != nil {
|
||||
secondaryColor = .clear
|
||||
}
|
||||
if dashTertiaryColor != nil {
|
||||
tertiaryColor = .clear
|
||||
}
|
||||
}
|
||||
dustColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
case .standalone:
|
||||
@ -225,6 +233,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
if dashSecondaryColor != nil {
|
||||
secondaryColor = .clear
|
||||
}
|
||||
if dashTertiaryColor != nil {
|
||||
tertiaryColor = .clear
|
||||
}
|
||||
|
||||
mainColor = serviceMessageColorComponents(chatTheme: arguments.presentationData.theme.theme.chat, wallpaper: arguments.presentationData.theme.wallpaper).primaryText
|
||||
dustColor = titleColor
|
||||
@ -754,6 +765,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
size: backgroundFrame.size,
|
||||
primaryColor: mainColor,
|
||||
secondaryColor: secondaryColor,
|
||||
thirdColor: tertiaryColor,
|
||||
pattern: pattern,
|
||||
animation: animation
|
||||
)
|
||||
|
||||
@ -780,7 +780,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
let availableForwardWidth = max(60.0, availableWidth + 6.0)
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||
}
|
||||
|
||||
var needsReplyBackground = false
|
||||
|
||||
@ -382,16 +382,22 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let author = item.message.author
|
||||
let mainColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
var secondaryColor: UIColor? = nil
|
||||
var tertiaryColor: UIColor? = nil
|
||||
|
||||
let nameColors = author?.nameColor.flatMap { item.context.peerNameColors.get($0) }
|
||||
if !incoming {
|
||||
mainColor = messageTheme.accentTextColor
|
||||
if let _ = author?.nameColor?.dashColors.1 {
|
||||
if let _ = nameColors?.secondary {
|
||||
secondaryColor = .clear
|
||||
}
|
||||
if let _ = nameColors?.tertiary {
|
||||
tertiaryColor = .clear
|
||||
}
|
||||
} else {
|
||||
var authorNameColor: UIColor?
|
||||
authorNameColor = author?.nameColor?.color
|
||||
secondaryColor = author?.nameColor?.dashColors.1
|
||||
let authorNameColor = nameColors?.main
|
||||
secondaryColor = nameColors?.secondary
|
||||
tertiaryColor = nameColors?.tertiary
|
||||
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
@ -400,7 +406,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseQuoteTintColor: mainColor, baseQuoteSecondaryTintColor: secondaryColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont, underlineLinks: underlineLinks, message: item.message, adjustQuoteFontSize: true)
|
||||
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseQuoteTintColor: mainColor, baseQuoteSecondaryTintColor: secondaryColor, baseQuoteTertiaryTintColor: tertiaryColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont, underlineLinks: underlineLinks, message: item.message, adjustQuoteFontSize: true)
|
||||
} else if !rawText.isEmpty {
|
||||
attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor)
|
||||
} else {
|
||||
|
||||
@ -1873,7 +1873,9 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var text: String = ""
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
let rawText = self.presentationData.strings.Channel_AdminLog_MessageChangedNameColorSet(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", "\(updatedValue)")
|
||||
let _ = updatedValue
|
||||
|
||||
let rawText = self.presentationData.strings.Channel_AdminLog_MessageChangedNameColorSet(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", "●")
|
||||
|
||||
appendAttributedText(text: rawText, generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
@ -1899,17 +1901,26 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var text: String = ""
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
let rawText = self.presentationData.strings.Channel_AdminLog_MessageChangedBackgroundEmojiSet(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", "\(updatedValue ?? 0)")
|
||||
|
||||
appendAttributedText(text: rawText, generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
} else if index == 1 {
|
||||
return [.Bold]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
|
||||
if let updatedValue, updatedValue != 0 {
|
||||
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedBackgroundEmojiSet(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)]
|
||||
} else if index == 1 {
|
||||
return [.CustomEmoji(stickerPack: nil, fileId: updatedValue)]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
} else {
|
||||
let rawText = self.presentationData.strings.Channel_AdminLog_MessageChangedBackgroundEmojiRemoved(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
|
||||
|
||||
appendAttributedText(text: rawText, 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)
|
||||
|
||||
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: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
|
||||
@ -92,24 +92,31 @@ private let dashBackgroundTemplateImage: UIImage = {
|
||||
return generateDashBackgroundTemplateImage()
|
||||
}()
|
||||
|
||||
private func generateDashTemplateImage(isMonochrome: Bool) -> UIImage {
|
||||
private func generateDashTemplateImage(isMonochrome: Bool, isTriple: Bool) -> UIImage {
|
||||
return generateImage(CGSize(width: radius * 2.0, height: 18.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
let dashOffset: CGFloat = isMonochrome ? -4.0 : 5.0
|
||||
let dashOffset: CGFloat
|
||||
if isTriple {
|
||||
dashOffset = isMonochrome ? -2.0 : 0.0
|
||||
} else {
|
||||
dashOffset = isMonochrome ? -4.0 : 5.0
|
||||
}
|
||||
|
||||
let dashHeight: CGFloat = isTriple ? 6.0 : 9.0
|
||||
|
||||
context.translateBy(x: 0.0, y: dashOffset)
|
||||
|
||||
for _ in 0 ..< 2 {
|
||||
context.move(to: CGPoint(x: 0.0, y: 3.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 0.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: 9.0 + 3.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: dashHeight))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: dashHeight + 3.0))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
|
||||
context.translateBy(x: 0.0, y: 18.0)
|
||||
context.translateBy(x: 0.0, y: size.height)
|
||||
}
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(x: lineWidth, y: 0.0), size: CGSize(width: size.width - lineWidth, height: size.height)))
|
||||
@ -117,11 +124,19 @@ private func generateDashTemplateImage(isMonochrome: Bool) -> UIImage {
|
||||
}
|
||||
|
||||
private let dashOpaqueTemplateImage: UIImage = {
|
||||
return generateDashTemplateImage(isMonochrome: false)
|
||||
return generateDashTemplateImage(isMonochrome: false, isTriple: false)
|
||||
}()
|
||||
|
||||
private let dashOpaqueTripleTemplateImage: UIImage = {
|
||||
return generateDashTemplateImage(isMonochrome: false, isTriple: true)
|
||||
}()
|
||||
|
||||
private let dashMonochromeTemplateImage: UIImage = {
|
||||
return generateDashTemplateImage(isMonochrome: true)
|
||||
return generateDashTemplateImage(isMonochrome: true, isTriple: false)
|
||||
}()
|
||||
|
||||
private let dashMonochromeTripleTemplateImage: UIImage = {
|
||||
return generateDashTemplateImage(isMonochrome: true, isTriple: true)
|
||||
}()
|
||||
|
||||
private func generateGradient(gradientWidth: CGFloat, baseAlpha: CGFloat) -> UIImage {
|
||||
@ -175,6 +190,7 @@ private final class PatternContentsTarget: MultiAnimationRenderTarget {
|
||||
private final class LineView: UIView {
|
||||
private let backgroundView: UIImageView
|
||||
private var dashBackgroundView: UIImageView?
|
||||
private var dashThirdBackgroundView: UIImageView?
|
||||
|
||||
private var params: Params?
|
||||
private var isAnimating: Bool = false
|
||||
@ -183,12 +199,14 @@ private final class LineView: UIView {
|
||||
var size: CGSize
|
||||
var primaryColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
var thirdColor: UIColor?
|
||||
var displayProgress: Bool
|
||||
|
||||
init(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, displayProgress: Bool) {
|
||||
init(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, thirdColor: UIColor?, displayProgress: Bool) {
|
||||
self.size = size
|
||||
self.primaryColor = primaryColor
|
||||
self.secondaryColor = secondaryColor
|
||||
self.thirdColor = thirdColor
|
||||
self.displayProgress = displayProgress
|
||||
}
|
||||
}
|
||||
@ -231,6 +249,21 @@ private final class LineView: UIView {
|
||||
}
|
||||
dashBackgroundView.layer.add(animation, forKey: "progress")
|
||||
}
|
||||
if let dashThirdBackgroundView = self.dashThirdBackgroundView {
|
||||
if dashThirdBackgroundView.layer.animation(forKey: "progress") == nil {
|
||||
let animation = dashThirdBackgroundView.layer.makeAnimation(from: 18.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = 1.0
|
||||
self.isAnimating = true
|
||||
animation.completion = { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isAnimating = false
|
||||
self.updateAnimations()
|
||||
}
|
||||
dashThirdBackgroundView.layer.add(animation, forKey: "progress")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let phaseDuration: Double = 1.0
|
||||
if self.backgroundView.layer.animation(forKey: "progress") == nil {
|
||||
@ -271,11 +304,12 @@ private final class LineView: UIView {
|
||||
self.layer.masksToBounds = params.secondaryColor != nil || self.isAnimating
|
||||
}
|
||||
|
||||
func update(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, displayProgress: Bool, animation: ListViewItemUpdateAnimation) {
|
||||
func update(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, thirdColor: UIColor?, displayProgress: Bool, animation: ListViewItemUpdateAnimation) {
|
||||
let params = Params(
|
||||
size: size,
|
||||
primaryColor: primaryColor,
|
||||
secondaryColor: secondaryColor,
|
||||
thirdColor: thirdColor,
|
||||
displayProgress: displayProgress
|
||||
)
|
||||
if self.params == params {
|
||||
@ -305,13 +339,52 @@ private final class LineView: UIView {
|
||||
dashBackgroundView.frame = dashBackgroundFrame
|
||||
}
|
||||
|
||||
let templateImage: UIImage
|
||||
let monochromeTemplateImage: UIImage
|
||||
|
||||
if let thirdColor {
|
||||
let thirdDashBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -12.0), size: CGSize(width: radius * 2.0, height: size.height + 18.0))
|
||||
templateImage = dashOpaqueTripleTemplateImage
|
||||
monochromeTemplateImage = dashMonochromeTripleTemplateImage
|
||||
|
||||
let dashThirdBackgroundView: UIImageView
|
||||
if let current = self.dashThirdBackgroundView {
|
||||
dashThirdBackgroundView = current
|
||||
|
||||
animation.animator.updateFrame(layer: dashThirdBackgroundView.layer, frame: thirdDashBackgroundFrame, completion: nil)
|
||||
} else {
|
||||
dashThirdBackgroundView = UIImageView()
|
||||
self.dashThirdBackgroundView = dashThirdBackgroundView
|
||||
self.addSubview(dashThirdBackgroundView)
|
||||
|
||||
dashThirdBackgroundView.frame = thirdDashBackgroundFrame
|
||||
}
|
||||
|
||||
if thirdColor.alpha == 0.0 {
|
||||
dashThirdBackgroundView.alpha = 0.4
|
||||
dashThirdBackgroundView.image = monochromeTemplateImage
|
||||
dashThirdBackgroundView.tintColor = primaryColor
|
||||
} else {
|
||||
dashThirdBackgroundView.alpha = 1.0
|
||||
dashThirdBackgroundView.image = templateImage
|
||||
dashThirdBackgroundView.tintColor = thirdColor
|
||||
}
|
||||
} else {
|
||||
templateImage = dashOpaqueTemplateImage
|
||||
monochromeTemplateImage = dashMonochromeTemplateImage
|
||||
if let dashThirdBackgroundView = self.dashThirdBackgroundView {
|
||||
self.dashThirdBackgroundView = nil
|
||||
dashThirdBackgroundView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if secondaryColor.alpha == 0.0 {
|
||||
self.backgroundView.alpha = 0.2
|
||||
dashBackgroundView.image = dashMonochromeTemplateImage
|
||||
dashBackgroundView.image = monochromeTemplateImage
|
||||
dashBackgroundView.tintColor = primaryColor
|
||||
} else {
|
||||
self.backgroundView.alpha = 1.0
|
||||
dashBackgroundView.image = dashOpaqueTemplateImage
|
||||
dashBackgroundView.image = templateImage
|
||||
dashBackgroundView.tintColor = secondaryColor
|
||||
}
|
||||
} else {
|
||||
@ -365,6 +438,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
var size: CGSize
|
||||
var primaryColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
var thirdColor: UIColor?
|
||||
var pattern: Pattern?
|
||||
var displayProgress: Bool
|
||||
|
||||
@ -372,12 +446,14 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
size: CGSize,
|
||||
primaryColor: UIColor,
|
||||
secondaryColor: UIColor?,
|
||||
thirdColor: UIColor?,
|
||||
pattern: Pattern?,
|
||||
displayProgress: Bool
|
||||
) {
|
||||
self.size = size
|
||||
self.primaryColor = primaryColor
|
||||
self.secondaryColor = secondaryColor
|
||||
self.thirdColor = thirdColor
|
||||
self.pattern = pattern
|
||||
self.displayProgress = displayProgress
|
||||
}
|
||||
@ -393,6 +469,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
size: params.size,
|
||||
primaryColor: params.primaryColor,
|
||||
secondaryColor: params.secondaryColor,
|
||||
thirdColor: params.thirdColor,
|
||||
pattern: params.pattern,
|
||||
animation: .None
|
||||
)
|
||||
@ -502,6 +579,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
size: CGSize,
|
||||
primaryColor: UIColor,
|
||||
secondaryColor: UIColor?,
|
||||
thirdColor: UIColor?,
|
||||
pattern: Pattern?,
|
||||
animation: ListViewItemUpdateAnimation
|
||||
) {
|
||||
@ -509,6 +587,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
size: size,
|
||||
primaryColor: primaryColor,
|
||||
secondaryColor: secondaryColor,
|
||||
thirdColor: thirdColor,
|
||||
pattern: pattern,
|
||||
displayProgress: self.displayProgress
|
||||
)
|
||||
@ -588,6 +667,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
size: lineFrame.size,
|
||||
primaryColor: params.primaryColor,
|
||||
secondaryColor: params.secondaryColor,
|
||||
thirdColor: params.thirdColor,
|
||||
displayProgress: params.displayProgress,
|
||||
animation: animation
|
||||
)
|
||||
|
||||
@ -9,25 +9,26 @@ import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
|
||||
private enum PeerNameColorEntryId: Hashable {
|
||||
case color(Int32)
|
||||
}
|
||||
|
||||
private enum PeerNameColorEntry: Comparable, Identifiable {
|
||||
case color(Int, PeerNameColor, Bool)
|
||||
case color(Int, PeerNameColor, PeerNameColors.Colors, Bool)
|
||||
|
||||
var stableId: PeerNameColorEntryId {
|
||||
switch self {
|
||||
case let .color(_, color, _):
|
||||
case let .color(_, color, _, _):
|
||||
return .color(color.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerNameColorEntry, rhs: PeerNameColorEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .color(lhsIndex, lhsAccentColor, lhsSelected):
|
||||
if case let .color(rhsIndex, rhsAccentColor, rhsSelected) = rhs, lhsIndex == rhsIndex, lhsAccentColor == rhsAccentColor, lhsSelected == rhsSelected {
|
||||
case let .color(lhsIndex, lhsColor, lhsAccentColor, lhsSelected):
|
||||
if case let .color(rhsIndex, rhsColor, rhsAccentColor, rhsSelected) = rhs, lhsIndex == rhsIndex, lhsColor == rhsColor, lhsAccentColor == rhsAccentColor, lhsSelected == rhsSelected {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -37,9 +38,9 @@ private enum PeerNameColorEntry: Comparable, Identifiable {
|
||||
|
||||
static func <(lhs: PeerNameColorEntry, rhs: PeerNameColorEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .color(lhsIndex, _, _):
|
||||
case let .color(lhsIndex, _, _, _):
|
||||
switch rhs {
|
||||
case let .color(rhsIndex, _, _):
|
||||
case let .color(rhsIndex, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
@ -47,20 +48,22 @@ private enum PeerNameColorEntry: Comparable, Identifiable {
|
||||
|
||||
func item(action: @escaping (PeerNameColor) -> Void) -> ListViewItem {
|
||||
switch self {
|
||||
case let .color(_, color, selected):
|
||||
return PeerNameColorIconItem(color: color, selected: selected, action: action)
|
||||
case let .color(_, index, colors, selected):
|
||||
return PeerNameColorIconItem(index: index, colors: colors, selected: selected, action: action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class PeerNameColorIconItem: ListViewItem {
|
||||
let color: PeerNameColor
|
||||
let index: PeerNameColor
|
||||
let colors: PeerNameColors.Colors
|
||||
let selected: Bool
|
||||
let action: (PeerNameColor) -> Void
|
||||
|
||||
public init(color: PeerNameColor, selected: Bool, action: @escaping (PeerNameColor) -> Void) {
|
||||
self.color = color
|
||||
public init(index: PeerNameColor, colors: PeerNameColors.Colors, selected: Bool, action: @escaping (PeerNameColor) -> Void) {
|
||||
self.index = index
|
||||
self.colors = colors
|
||||
self.selected = selected
|
||||
self.action = action
|
||||
}
|
||||
@ -107,22 +110,22 @@ private class PeerNameColorIconItem: ListViewItem {
|
||||
|
||||
public var selectable = true
|
||||
public func selected(listView: ListView) {
|
||||
self.action(self.color)
|
||||
self.action(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
private func generateRingImage(nameColor: PeerNameColor) -> UIImage? {
|
||||
private func generateRingImage(nameColor: PeerNameColors.Colors) -> UIImage? {
|
||||
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setStrokeColor(nameColor.color.cgColor)
|
||||
context.setStrokeColor(nameColor.main.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
|
||||
})
|
||||
}
|
||||
|
||||
private func generateFillImage(nameColor: PeerNameColor) -> UIImage? {
|
||||
private func generateFillImage(nameColor: PeerNameColors.Colors) -> UIImage? {
|
||||
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
@ -131,8 +134,7 @@ private func generateFillImage(nameColor: PeerNameColor) -> UIImage? {
|
||||
context.addEllipse(in: circleBounds)
|
||||
context.clip()
|
||||
|
||||
let (firstColor, secondColor) = nameColor.dashColors
|
||||
if let secondColor {
|
||||
if let secondColor = nameColor.secondary {
|
||||
context.setFillColor(secondColor.cgColor)
|
||||
context.fill(circleBounds)
|
||||
|
||||
@ -140,10 +142,20 @@ private func generateFillImage(nameColor: PeerNameColor) -> UIImage? {
|
||||
context.addLine(to: CGPoint(x: size.width, y: 0.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: size.height))
|
||||
context.closePath()
|
||||
context.setFillColor(firstColor.cgColor)
|
||||
context.setFillColor(nameColor.main.cgColor)
|
||||
context.fillPath()
|
||||
|
||||
if let thirdColor = nameColor.tertiary {
|
||||
context.setFillColor(thirdColor.cgColor)
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.rotate(by: .pi / 4.0)
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: -9.0, y: -9.0), size: CGSize(width: 18.0, height: 18.0)), cornerRadius: 4.0)
|
||||
context.addPath(path.cgPath)
|
||||
context.fillPath()
|
||||
}
|
||||
} else {
|
||||
context.setFillColor(firstColor.cgColor)
|
||||
context.setFillColor(nameColor.main.cgColor)
|
||||
context.fill(circleBounds)
|
||||
}
|
||||
})
|
||||
@ -198,7 +210,7 @@ private final class PeerNameColorIconItemNode : ListViewItemNode {
|
||||
var updatedAccentColor = false
|
||||
var updatedSelected = false
|
||||
|
||||
if currentItem == nil || currentItem?.color != item.color {
|
||||
if currentItem == nil || currentItem?.colors != item.colors {
|
||||
updatedAccentColor = true
|
||||
}
|
||||
if currentItem?.selected != item.selected {
|
||||
@ -211,8 +223,8 @@ private final class PeerNameColorIconItemNode : ListViewItemNode {
|
||||
strongSelf.item = item
|
||||
|
||||
if updatedAccentColor {
|
||||
strongSelf.fillNode.image = generateFillImage(nameColor: item.color)
|
||||
strongSelf.ringNode.image = generateRingImage(nameColor: item.color)
|
||||
strongSelf.fillNode.image = generateFillImage(nameColor: item.colors)
|
||||
strongSelf.ringNode.image = generateRingImage(nameColor: item.colors)
|
||||
}
|
||||
|
||||
let center = CGPoint(x: 30.0, y: 28.0)
|
||||
@ -256,12 +268,12 @@ final class PeerNameColorItem: ListViewItem, ItemListItem {
|
||||
var sectionId: ItemListSectionId
|
||||
|
||||
let theme: PresentationTheme
|
||||
let colors: [PeerNameColor]
|
||||
let colors: PeerNameColors
|
||||
let currentColor: PeerNameColor
|
||||
let updated: (PeerNameColor) -> Void
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, colors: [PeerNameColor], currentColor: PeerNameColor, updated: @escaping (PeerNameColor) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) {
|
||||
init(theme: PresentationTheme, colors: PeerNameColors, currentColor: PeerNameColor, updated: @escaping (PeerNameColor) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.colors = colors
|
||||
self.currentColor = currentColor
|
||||
@ -325,7 +337,7 @@ private func ensureColorVisible(listNode: ListView, color: PeerNameColor, animat
|
||||
var resultNode: PeerNameColorIconItemNode?
|
||||
listNode.forEachItemNode { node in
|
||||
if resultNode == nil, let node = node as? PeerNameColorIconItemNode {
|
||||
if node.item?.color == color {
|
||||
if node.item?.index == color {
|
||||
resultNode = node
|
||||
}
|
||||
}
|
||||
@ -406,7 +418,7 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let options = ListViewDeleteAndInsertOptions()
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if !self.initialized || transition.updatePosition || !self.tapping {
|
||||
if let index = item.colors.firstIndex(where: { $0 == item.currentColor }) {
|
||||
if let index = item.colors.displayOrder.firstIndex(where: { $0 == item.currentColor.rawValue }) {
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-70.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
||||
self.initialized = true
|
||||
}
|
||||
@ -501,10 +513,13 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
var entries: [PeerNameColorEntry] = []
|
||||
|
||||
var index: Int = 0
|
||||
for color in item.colors {
|
||||
entries.append(.color(index, color, color == item.currentColor))
|
||||
index += 1
|
||||
var i: Int = 0
|
||||
for index in item.colors.displayOrder {
|
||||
let color = PeerNameColor(rawValue: index)
|
||||
if let colors = item.colors.colors[index] {
|
||||
entries.append(.color(i, color, colors, color == item.currentColor))
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
let action: (PeerNameColor) -> Void = { [weak self] color in
|
||||
|
||||
@ -47,7 +47,7 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry {
|
||||
|
||||
case colorHeader(String)
|
||||
case colorMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, items: [PeerNameColorChatPreviewItem.MessageItem])
|
||||
case colorPicker(colors: [PeerNameColor], currentColor: PeerNameColor)
|
||||
case colorPicker(colors: PeerNameColors, currentColor: PeerNameColor)
|
||||
case colorDescription(String)
|
||||
case backgroundEmojiHeader(String)
|
||||
case backgroundEmoji(EmojiPagerContentComponent, UIColor)
|
||||
@ -185,6 +185,7 @@ private struct PeerNameColorScreenState: Equatable {
|
||||
}
|
||||
|
||||
private func peerNameColorScreenEntries(
|
||||
nameColors: PeerNameColors,
|
||||
presentationData: PresentationData,
|
||||
state: PeerNameColorScreenState,
|
||||
peer: EnginePeer?,
|
||||
@ -194,11 +195,6 @@ private func peerNameColorScreenEntries(
|
||||
var entries: [PeerNameColorScreenEntry] = []
|
||||
|
||||
if let peer {
|
||||
var allColors: [PeerNameColor] = [
|
||||
.blue
|
||||
]
|
||||
allColors.append(contentsOf: PeerNameColor.allCases.filter { $0 != .blue})
|
||||
|
||||
let nameColor: PeerNameColor
|
||||
if let updatedNameColor = state.updatedNameColor {
|
||||
nameColor = updatedNameColor
|
||||
@ -208,6 +204,8 @@ private func peerNameColorScreenEntries(
|
||||
nameColor = .blue
|
||||
}
|
||||
|
||||
let colors = nameColors.get(nameColor)
|
||||
|
||||
let backgroundEmojiId: Int64?
|
||||
if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId {
|
||||
if updatedBackgroundEmojiId == 0 {
|
||||
@ -250,14 +248,14 @@ private func peerNameColorScreenEntries(
|
||||
items: [messageItem]
|
||||
))
|
||||
entries.append(.colorPicker(
|
||||
colors: allColors,
|
||||
colors: nameColors,
|
||||
currentColor: nameColor
|
||||
))
|
||||
entries.append(.colorDescription(presentationData.strings.NameColor_ChatPreview_Description_Account))
|
||||
|
||||
if let emojiContent {
|
||||
entries.append(.backgroundEmojiHeader(presentationData.strings.NameColor_BackgroundEmoji_Title))
|
||||
entries.append(.backgroundEmoji(emojiContent, nameColor.color))
|
||||
entries.append(.backgroundEmoji(emojiContent, colors.main))
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,6 +312,7 @@ public func PeerNameColorScreen(
|
||||
peerId = channelId
|
||||
}
|
||||
|
||||
|
||||
let emojiContent = combineLatest(
|
||||
statePromise.get(),
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
@ -325,12 +324,13 @@ public func PeerNameColorScreen(
|
||||
} else {
|
||||
selectedEmojiId = peer?.backgroundEmojiId
|
||||
}
|
||||
let nameColor: UIColor
|
||||
let nameColor: PeerNameColor
|
||||
if let updatedNameColor = state.updatedNameColor {
|
||||
nameColor = updatedNameColor.color
|
||||
nameColor = updatedNameColor
|
||||
} else {
|
||||
nameColor = (peer?.nameColor ?? .blue).color
|
||||
nameColor = (peer?.nameColor ?? .blue)
|
||||
}
|
||||
let color = context.peerNameColors.get(nameColor)
|
||||
|
||||
let selectedItems: [EngineMedia.Id]
|
||||
if let selectedEmojiId, selectedEmojiId != 0 {
|
||||
@ -351,7 +351,7 @@ public func PeerNameColorScreen(
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: context.account.peerId,
|
||||
selectedItems: Set(selectedItems),
|
||||
backgroundIconColor: nameColor
|
||||
backgroundIconColor: color.main
|
||||
)
|
||||
}
|
||||
|
||||
@ -547,6 +547,7 @@ public func PeerNameColorScreen(
|
||||
)
|
||||
|
||||
let entries = peerNameColorScreenEntries(
|
||||
nameColors: context.peerNameColors,
|
||||
presentationData: presentationData,
|
||||
state: state,
|
||||
peer: peer,
|
||||
|
||||
@ -25,7 +25,6 @@ import TelegramUIPreferences
|
||||
import UndoUI
|
||||
import TelegramStringFormatting
|
||||
|
||||
//TODO:localize
|
||||
final class ShareWithPeersScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
@ -941,9 +940,9 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
|
||||
} else if section.id == 1 {
|
||||
if case .members = component.stateContext.subject {
|
||||
sectionTitle = "SUBSCRIBERS"
|
||||
sectionTitle = environment.strings.BoostGift_Subscribers_SectionTitle
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
sectionTitle = "CHANNELS"
|
||||
sectionTitle = environment.strings.BoostGift_Channels_SectionTitle
|
||||
} else {
|
||||
sectionTitle = environment.strings.Story_Privacy_ContactsHeader
|
||||
}
|
||||
@ -1390,7 +1389,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
} else {
|
||||
if case .members = component.stateContext.subject {
|
||||
if let invitedAt = stateValue.invitedAt[peer.id] {
|
||||
subtitle = "joined \(stringForMediumDate(timestamp: invitedAt, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat))"
|
||||
subtitle = environment.strings.BoostGift_Subscribers_Joined(stringForMediumDate(timestamp: invitedAt, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat)).string
|
||||
} else {
|
||||
subtitle = nil
|
||||
}
|
||||
@ -1445,7 +1444,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
self.hapticFeedback.error()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "You can select maximum \(component.context.userLimits.maxGiveawayChannelsCount) channels.", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: environment.strings.BoostGift_Channels_MaximumReached("\(component.context.userLimits.maxGiveawayChannelsCount)").string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
return
|
||||
}
|
||||
if case .channels = component.stateContext.subject {
|
||||
@ -1453,11 +1452,11 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
let alertController = textAlertController(
|
||||
context: component.context,
|
||||
forceTheme: environment.theme,
|
||||
title: "Channel is Private",
|
||||
text: "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.",
|
||||
title: environment.strings.BoostGift_Channels_PrivateChannel_Title,
|
||||
text: environment.strings.BoostGift_Channels_PrivateChannel_Text,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: "Add", action: {
|
||||
TextAlertAction(type: .defaultAction, title: environment.strings.BoostGift_Channels_PrivateChannel_Add, action: {
|
||||
togglePeer()
|
||||
})
|
||||
]
|
||||
@ -1475,7 +1474,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
self.hapticFeedback.error()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "You can select maximum 10 subscribers.", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: environment.strings.BoostGift_Subscribers_MaximumReached("\(10)").string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
return
|
||||
}
|
||||
togglePeer()
|
||||
@ -2018,9 +2017,9 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
let placeholder: String
|
||||
switch component.stateContext.subject {
|
||||
case .members:
|
||||
placeholder = "Search Subscribers"
|
||||
placeholder = environment.strings.BoostGift_Subscribers_Search
|
||||
case .channels:
|
||||
placeholder = "Search Channels"
|
||||
placeholder = environment.strings.BoostGift_Channels_Search
|
||||
case .chats:
|
||||
placeholder = environment.strings.Story_Privacy_SearchChats
|
||||
default:
|
||||
@ -2367,13 +2366,13 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
case .contactsSearch:
|
||||
title = ""
|
||||
case .members:
|
||||
title = "Gift Premium"
|
||||
actionButtonTitle = "Save Recipients"
|
||||
subtitle = "select up to 10 subscribers"
|
||||
title = environment.strings.BoostGift_Subscribers_Title
|
||||
subtitle = environment.strings.BoostGift_Subscribers_Subtitle("\(10)").string
|
||||
actionButtonTitle = environment.strings.BoostGift_Subscribers_Save
|
||||
case .channels:
|
||||
title = "Add Channels"
|
||||
actionButtonTitle = "Save Channels"
|
||||
subtitle = "select up to \(component.context.userLimits.maxGiveawayChannelsCount) channels"
|
||||
title = environment.strings.BoostGift_Channels_Title
|
||||
subtitle = environment.strings.BoostGift_Channels_Subtitle("\(component.context.userLimits.maxGiveawayChannelsCount)").string
|
||||
actionButtonTitle = environment.strings.BoostGift_Channels_Save
|
||||
}
|
||||
|
||||
let titleComponent: AnyComponent<Empty>
|
||||
|
||||
@ -180,8 +180,6 @@ final class MediaNavigationStripComponent: Component {
|
||||
let itemHeight: CGFloat = 2.0
|
||||
let minItemWidth: CGFloat = 2.0
|
||||
|
||||
var size = CGSize(width: availableSize.width, height: itemHeight)
|
||||
|
||||
var didSetCompletion = false
|
||||
|
||||
var validIndices: [Int] = []
|
||||
@ -223,7 +221,6 @@ final class MediaNavigationStripComponent: Component {
|
||||
var itemFrame = CGRect(origin: CGPoint(x: -globalOffset + CGFloat(i) * (itemWidth + spacing), y: 0.0), size: CGSize(width: itemWidth, height: itemHeight))
|
||||
if component.isSeeking {
|
||||
itemFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: 6.0))
|
||||
size.height = itemFrame.height
|
||||
}
|
||||
if itemFrame.maxX < 0.0 || itemFrame.minX >= availableSize.width {
|
||||
continue
|
||||
|
||||
@ -546,6 +546,14 @@ private final class StoryContainerScreenComponent: Component {
|
||||
}
|
||||
self.initialSeekTimestamp = nil
|
||||
self.previousSeekTime = nil
|
||||
|
||||
guard let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
|
||||
return
|
||||
}
|
||||
guard let visibleItemView = itemSetComponentView.visibleItems[slice.item.storyItem.id]?.view.view as? StoryItemContentComponent.View else {
|
||||
return
|
||||
}
|
||||
visibleItemView.seekEnded()
|
||||
}
|
||||
longPressRecognizer.shouldBegin = { [weak self] touch in
|
||||
guard let self else {
|
||||
|
||||
@ -95,12 +95,13 @@ final class StoryInteractionGuideComponent: Component {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let strings = component.strings
|
||||
|
||||
let sideInset: CGFloat = 48.0
|
||||
|
||||
//TODO:localize
|
||||
let titleSize = self.titleLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Watching Stories", font: Font.semibold(20.0), textColor: .white, paragraphAlignment: .center)))),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Story_Guide_Title, font: Font.semibold(20.0), textColor: .white, paragraphAlignment: .center)))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
@ -114,7 +115,7 @@ final class StoryInteractionGuideComponent: Component {
|
||||
|
||||
let textSize = self.descriptionLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(text: .plain(NSAttributedString(string: "You can use these gestures to control playback.", font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.6), paragraphAlignment: .center)), maximumNumberOfLines: 0, lineSpacing: 0.2)),
|
||||
component: AnyComponent(BalancedTextComponent(text: .plain(NSAttributedString(string: strings.Story_Guide_Description, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.6), paragraphAlignment: .center)), maximumNumberOfLines: 0, lineSpacing: 0.2)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
@ -132,8 +133,8 @@ final class StoryInteractionGuideComponent: Component {
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Go forward",
|
||||
text: "Tap the screen",
|
||||
title: strings.Story_Guide_ForwardTitle,
|
||||
text: strings.Story_Guide_ForwardDescription,
|
||||
animationName: "story_forward",
|
||||
isPlaying: self.currentIndex == 0,
|
||||
playbackCompleted: { [weak self] in
|
||||
@ -151,8 +152,8 @@ final class StoryInteractionGuideComponent: Component {
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Pause and Seek",
|
||||
text: "Hold and move sideways",
|
||||
title: strings.Story_Guide_PauseTitle,
|
||||
text: strings.Story_Guide_PauseDescription,
|
||||
animationName: "story_pause",
|
||||
isPlaying: self.currentIndex == 1,
|
||||
playbackCompleted: { [weak self] in
|
||||
@ -170,8 +171,8 @@ final class StoryInteractionGuideComponent: Component {
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Go back",
|
||||
text: "Tap the left edge",
|
||||
title: strings.Story_Guide_BackTitle,
|
||||
text: strings.Story_Guide_BackDescription,
|
||||
animationName: "story_back",
|
||||
isPlaying: self.currentIndex == 2,
|
||||
playbackCompleted: { [weak self] in
|
||||
@ -189,8 +190,8 @@ final class StoryInteractionGuideComponent: Component {
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Move between stories",
|
||||
text: "Swipe left or right",
|
||||
title: strings.Story_Guide_MoveTitle,
|
||||
text: strings.Story_Guide_MoveDescription,
|
||||
animationName: "story_move",
|
||||
isPlaying: self.currentIndex == 3,
|
||||
playbackCompleted: { [weak self] in
|
||||
@ -222,7 +223,7 @@ final class StoryInteractionGuideComponent: Component {
|
||||
let buttonSize = self.proceedButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Tap to keep watching", font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)))),
|
||||
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Story_Guide_Proceed, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)))),
|
||||
action: { [weak self] in
|
||||
self?.handleTap()
|
||||
}
|
||||
|
||||
@ -277,7 +277,9 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
|
||||
self.videoPlaybackStatus = status
|
||||
self.updateVideoPlaybackProgress()
|
||||
if !self.isSeeking {
|
||||
self.updateVideoPlaybackProgress()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -360,7 +362,9 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
|
||||
if case .file = self.currentMessageMedia {
|
||||
self.updateVideoPlaybackProgress()
|
||||
if !self.isSeeking {
|
||||
self.updateVideoPlaybackProgress()
|
||||
}
|
||||
} else {
|
||||
if !self.markedAsSeen {
|
||||
self.markedAsSeen = true
|
||||
@ -536,6 +540,7 @@ final class StoryItemContentComponent: Component {
|
||||
)
|
||||
}
|
||||
|
||||
private var isSeeking = false
|
||||
func seekTo(_ timestamp: Double, apply: Bool) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return
|
||||
@ -543,8 +548,13 @@ final class StoryItemContentComponent: Component {
|
||||
if apply {
|
||||
videoNode.seek(timestamp)
|
||||
}
|
||||
self.isSeeking = true
|
||||
self.updateVideoPlaybackProgress(timestamp)
|
||||
}
|
||||
|
||||
func seekEnded() {
|
||||
self.isSeeking = false
|
||||
}
|
||||
|
||||
func update(component: StoryItemContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StoryContentItem.Environment>, transition: Transition) -> CGSize {
|
||||
let previousItem = self.component?.item
|
||||
|
||||
@ -4708,7 +4708,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
let seekLabelSize = self.seekLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(text: "Slide left or right to seek", font: Font.semibold(14.0), color: .white)),
|
||||
component: AnyComponent(Text(text: component.strings.Story_SlideToSeek, font: Font.semibold(14.0), color: .white)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 92 KiB |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Coffee.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Duck.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 48 KiB |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Steam.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 72 KiB |
@ -244,6 +244,9 @@ public final class AccountContextImpl: AccountContext {
|
||||
private var userLimitsConfigurationDisposable: Disposable?
|
||||
public private(set) var userLimits: EngineConfiguration.UserLimits
|
||||
|
||||
private var peerNameColorsConfigurationDisposable: Disposable?
|
||||
public private(set) var peerNameColors: PeerNameColors
|
||||
|
||||
public private(set) var isPremium: Bool
|
||||
|
||||
public let imageCache: AnyObject?
|
||||
@ -257,6 +260,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.imageCache = DirectMediaImageCache(account: account)
|
||||
|
||||
self.userLimits = EngineConfiguration.UserLimits(UserLimitsConfiguration.defaultValue)
|
||||
self.peerNameColors = PeerNameColors.defaultValue
|
||||
self.isPremium = false
|
||||
|
||||
self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager)
|
||||
@ -336,7 +340,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
if !temp {
|
||||
let currentCountriesConfiguration = self.currentCountriesConfiguration
|
||||
self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: nil)
|
||||
|> deliverOnMainQueue).start(next: { value in
|
||||
|> deliverOnMainQueue).start(next: { value in
|
||||
let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value))
|
||||
})
|
||||
}
|
||||
@ -395,12 +399,20 @@ public final class AccountContextImpl: AccountContext {
|
||||
return (isPremium, userLimits)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isPremium, userLimits in
|
||||
guard let strongSelf = self else {
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] isPremium, userLimits in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isPremium = isPremium
|
||||
strongSelf.userLimits = userLimits
|
||||
self.isPremium = isPremium
|
||||
self.userLimits = userLimits
|
||||
})
|
||||
|
||||
self.peerNameColorsConfigurationDisposable = (self._appConfiguration.get()
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] appConfiguration in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.peerNameColors = PeerNameColors.with(appConfiguration: appConfiguration)
|
||||
})
|
||||
}
|
||||
|
||||
@ -413,6 +425,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.experimentalUISettingsDisposable?.dispose()
|
||||
self.animatedEmojiStickersDisposable?.dispose()
|
||||
self.userLimitsConfigurationDisposable?.dispose()
|
||||
self.peerNameColorsConfigurationDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func storeSecureIdPassword(password: String) {
|
||||
|
||||
@ -802,11 +802,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
icons.append(PresentationAppIcon(name: "Premium", imageName: "Premium", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumBlack", imageName: "PremiumBlack", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumTurbo", imageName: "PremiumTurbo", isPremium: true))
|
||||
|
||||
icons.append(PresentationAppIcon(name: "PremiumDuck", imageName: "PremiumDuck", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumCoffee", imageName: "PremiumCoffee", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumSteam", imageName: "PremiumSteam", isPremium: true))
|
||||
|
||||
|
||||
return icons
|
||||
} else {
|
||||
return []
|
||||
|
||||
@ -4807,17 +4807,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case let .ongoing(_, status):
|
||||
switch status {
|
||||
case .notAllowed:
|
||||
content = .info(title: nil, text: "You can't participate in this giveaway.", timeout: nil, customUndoText: "Learn More")
|
||||
content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_NotAllowed, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
|
||||
case .participating:
|
||||
content = .succeed(text: "You are participating in this giveaway.", timeout: nil, customUndoText: "Learn More")
|
||||
content = .succeed(text: self.presentationData.strings.Chat_Giveaway_Toast_Participating, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
|
||||
case .notQualified:
|
||||
content = .info(title: nil, text: "You are not qualified for this giveaway yet.", timeout: nil, customUndoText: "Learn More")
|
||||
content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_NotQualified, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
|
||||
case .almostOver:
|
||||
content = .info(title: nil, text: "The giveaway is almost over.", timeout: nil, customUndoText: "Learn More")
|
||||
content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_AlmostOver, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
|
||||
}
|
||||
case let .finished(status, _, _, _, _):
|
||||
let _ = status
|
||||
content = .info(title: nil, text: "The giveaway is ended.", timeout: nil, customUndoText: "Learn More")
|
||||
case .finished:
|
||||
content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_Ended, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
|
||||
}
|
||||
let controller = UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in
|
||||
if case .undo = action, let self {
|
||||
@ -5110,8 +5109,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
|
||||
var accountPeerColor: ChatPresentationInterfaceState.AccountPeerColor?
|
||||
if peerView.peers[peerView.peerId]?.nameColor?.dashColors.1 != nil {
|
||||
accountPeerColor = ChatPresentationInterfaceState.AccountPeerColor(isDashed: true)
|
||||
if let nameColor = peerView.peers[peerView.peerId]?.nameColor {
|
||||
let colors = strongSelf.context.peerNameColors.get(nameColor)
|
||||
var style: ChatPresentationInterfaceState.AccountPeerColor.Style = .solid
|
||||
if colors.tertiary != nil {
|
||||
style = .tripleDashed
|
||||
} else if colors.secondary != nil {
|
||||
style = .doubleDashed
|
||||
}
|
||||
accountPeerColor = ChatPresentationInterfaceState.AccountPeerColor(style: style)
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
|
||||
return state.updatedIsPremium(isPremium).updatedAccountPeerColor(accountPeerColor)
|
||||
@ -19577,150 +19583,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
func displayGiveawayStatusInfo(messageId: EngineMessage.Id, giveawayInfo: PremiumGiveawayInfo) {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] message in
|
||||
guard let self, let message, let giveaway = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway else {
|
||||
guard let self, let message else {
|
||||
return
|
||||
}
|
||||
var peerName = ""
|
||||
if let peerId = giveaway.channelPeerIds.first, let peer = message.peers[peerId] {
|
||||
peerName = EnginePeer(peer).compactDisplayTitle
|
||||
}
|
||||
|
||||
let untilDate = stringForDate(timestamp: giveaway.untilDate, strings: self.presentationData.strings)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
var warning: String?
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
var actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
|
||||
switch giveawayInfo {
|
||||
case let .ongoing(start, status):
|
||||
let startDate = stringForDate(timestamp: start, strings: self.presentationData.strings)
|
||||
|
||||
title = "About This Giveaway"
|
||||
|
||||
let intro: String
|
||||
if case .almostOver = status {
|
||||
intro = "The giveaway was sponsored by the admins of **\(peerName)**, who acquired **\(giveaway.quantity) Telegram Premium** subscriptions for **\(giveaway.months)** months for its followers."
|
||||
} else {
|
||||
intro = "The giveaway is sponsored by the admins of **\(peerName)**, who acquired **\(giveaway.quantity) Telegram Premium** subscriptions for **\(giveaway.months)** months for its followers."
|
||||
}
|
||||
|
||||
let ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(untilDate)**, Telegram will automatically select **\(giveaway.quantity)** random users that joined **\(peerName)** and **\(giveaway.channelPeerIds.count - 1)** other listed channels after **\(startDate)**."
|
||||
} else {
|
||||
ending = "On **\(untilDate)**, Telegram will automatically select **\(giveaway.quantity)** random users that joined **\(peerName)** after **\(startDate)**."
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(untilDate)**, Telegram will automatically select **\(giveaway.quantity)** random subscribers of **\(peerName)** and **\(giveaway.channelPeerIds.count - 1)** other listed channels."
|
||||
} else {
|
||||
ending = "On **\(untilDate)**, Telegram will automatically select **\(giveaway.quantity)** random subscribers of **\(peerName)**."
|
||||
}
|
||||
}
|
||||
|
||||
var participation: String
|
||||
switch status {
|
||||
case .notQualified:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = "To take part in this giveaway please join the channel **\(peerName)** (**\(giveaway.channelPeerIds.count - 1)** other listed channels) before **\(untilDate)**."
|
||||
} else {
|
||||
participation = "To take part in this giveaway please join the channel **\(peerName)** before **\(untilDate)**."
|
||||
}
|
||||
case let .notAllowed(reason):
|
||||
switch reason {
|
||||
case let .joinedTooEarly(joinedOn):
|
||||
let joinDate = stringForDate(timestamp: joinedOn, strings: self.presentationData.strings)
|
||||
participation = "You are not eligible to participate in this giveaway, because you joined this channel on **\(joinDate)**, which is before the contest started."
|
||||
case let .channelAdmin(adminId):
|
||||
let _ = adminId
|
||||
participation = "You are not eligible to participate in this giveaway, because you are an admin of participating channel (**\(peerName)**)."
|
||||
case let .disallowedCountry(countryCode):
|
||||
let _ = countryCode
|
||||
participation = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway."
|
||||
}
|
||||
case .participating:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = "You are participating in this giveaway, because you have joined the channel **\(peerName)** (**\(giveaway.channelPeerIds.count - 1)** other listed channels)."
|
||||
} else {
|
||||
participation = "You are participating in this giveaway, because you have joined the channel **\(peerName)**."
|
||||
}
|
||||
case .almostOver:
|
||||
participation = "The giveaway is over, preparing results."
|
||||
}
|
||||
|
||||
if !participation.isEmpty {
|
||||
participation = "\n\n\(participation)"
|
||||
}
|
||||
|
||||
text = "\(intro)\n\n\(ending)\(participation)"
|
||||
case let .finished(status, start, finish, _, activatedCount):
|
||||
let startDate = stringForDate(timestamp: start, strings: self.presentationData.strings)
|
||||
let finishDate = stringForDate(timestamp: finish, strings: self.presentationData.strings)
|
||||
title = "Giveaway Ended"
|
||||
|
||||
let intro = "The giveaway was sponsored by the admins of **\(peerName)**, who acquired **\(giveaway.quantity) Telegram Premium** subscriptions for **\(giveaway.months)** months for its followers."
|
||||
|
||||
var ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(finishDate)**, Telegram automatically selected **\(giveaway.quantity)** random users that joined **\(peerName)** and other listed channels after **\(startDate)**."
|
||||
} else {
|
||||
ending = "On **\(finishDate)**, Telegram automatically selected **\(giveaway.quantity)** random users that joined **\(peerName)** after **\(startDate)**."
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(finishDate)**, Telegram automatically selected **\(giveaway.quantity)** random subscribers of **\(peerName)** and other listed channels."
|
||||
} else {
|
||||
ending = "On **\(finishDate)**, Telegram automatically selected **\(giveaway.quantity)** random subscribers of **\(peerName)**."
|
||||
}
|
||||
}
|
||||
|
||||
if activatedCount > 0 {
|
||||
ending += " \(activatedCount) of the winners already used their gift links."
|
||||
}
|
||||
|
||||
var result: String
|
||||
switch status {
|
||||
case .refunded:
|
||||
result = ""
|
||||
warning = "The channel cancelled the prizes by reversing the payment for them."
|
||||
actions = [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Close, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
case .notWon:
|
||||
result = "\n\nYou didn't win a prize in this giveaway."
|
||||
case let .won(slug):
|
||||
result = "\n\nYou won a prize in this giveaway. 🏆"
|
||||
let _ = slug
|
||||
actions = [TextAlertAction(type: .defaultAction, title: "View My Prize", action: {
|
||||
dismissImpl?()
|
||||
}), TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
}
|
||||
|
||||
text = "\(intro)\n\n\(ending)\(result)"
|
||||
}
|
||||
|
||||
let alertController = giveawayInfoAlertController(
|
||||
context: self.context,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
title: title,
|
||||
text: text,
|
||||
warning: warning,
|
||||
actions: actions
|
||||
)
|
||||
self.present(alertController, in: .window(.root))
|
||||
|
||||
dismissImpl = { [weak alertController] in
|
||||
alertController?.dismissAnimated()
|
||||
if let controller = giveawayInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, message: message, giveawayInfo: giveawayInfo) {
|
||||
self.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1045,11 +1045,22 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
var baseFontSize: CGFloat = 17.0
|
||||
var keyboardAppearance: UIKeyboardAppearance = UIKeyboardAppearance.default
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
var lineStyle: ChatInputTextView.Theme.Quote.LineStyle = .solid
|
||||
if let accountPeerColor = presentationInterfaceState.accountPeerColor {
|
||||
switch accountPeerColor.style {
|
||||
case .solid:
|
||||
lineStyle = .solid
|
||||
case .doubleDashed:
|
||||
lineStyle = .doubleDashed
|
||||
case .tripleDashed:
|
||||
lineStyle = .tripleDashed
|
||||
}
|
||||
}
|
||||
textInputNode.textView.theme = ChatInputTextView.Theme(
|
||||
quote: ChatInputTextView.Theme.Quote(
|
||||
background: presentationInterfaceState.theme.list.itemAccentColor.withMultipliedAlpha(presentationInterfaceState.theme.overallDarkAppearance ? 0.2 : 0.1),
|
||||
foreground: presentationInterfaceState.theme.list.itemAccentColor,
|
||||
isDashed: presentationInterfaceState.accountPeerColor?.isDashed == true
|
||||
lineStyle: lineStyle
|
||||
)
|
||||
)
|
||||
|
||||
@ -1628,11 +1639,22 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
}
|
||||
|
||||
if let textInputNode = self.textInputNode {
|
||||
var lineStyle: ChatInputTextView.Theme.Quote.LineStyle = .solid
|
||||
if let accountPeerColor = interfaceState.accountPeerColor {
|
||||
switch accountPeerColor.style {
|
||||
case .solid:
|
||||
lineStyle = .solid
|
||||
case .doubleDashed:
|
||||
lineStyle = .doubleDashed
|
||||
case .tripleDashed:
|
||||
lineStyle = .tripleDashed
|
||||
}
|
||||
}
|
||||
textInputNode.textView.theme = ChatInputTextView.Theme(
|
||||
quote: ChatInputTextView.Theme.Quote(
|
||||
background: interfaceState.theme.list.itemAccentColor.withMultipliedAlpha(interfaceState.theme.overallDarkAppearance ? 0.2 : 0.1),
|
||||
foreground: interfaceState.theme.list.itemAccentColor,
|
||||
isDashed: interfaceState.accountPeerColor?.isDashed == true
|
||||
lineStyle: lineStyle
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -891,17 +891,26 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
forceDark = true
|
||||
}
|
||||
let _ = (context.engine.payments.checkPremiumGiftCode(slug: slug)
|
||||
|> deliverOnMainQueue).startStandalone(next: { giftCode in
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] giftCode in
|
||||
if let giftCode {
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumGiftCodeScreen(
|
||||
context: context,
|
||||
giftCode: giftCode,
|
||||
subject: .giftCode(giftCode),
|
||||
forceDark: forceDark,
|
||||
action: {
|
||||
dismissImpl?()
|
||||
|
||||
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
|
||||
action: { [weak navigationController] in
|
||||
let _ = (context.engine.payments.applyPremiumGiftCode(slug: slug)
|
||||
|> deliverOnMainQueue).startStandalone(completed: {
|
||||
dismissImpl?()
|
||||
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: forceDark, dismissed: nil)
|
||||
navigationController?.pushViewController(controller)
|
||||
if let controller = controller as? PremiumIntroScreen {
|
||||
Queue.mainQueue().after(0.3, {
|
||||
controller.animateSuccess()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
openPeer: { peer in
|
||||
if peer.id != context.account.peerId {
|
||||
@ -941,7 +950,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}
|
||||
)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
navigationController?.pushViewController(controller)
|
||||
} else {
|
||||
|
||||
@ -1621,7 +1621,8 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
||||
}
|
||||
|
||||
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemNameColor, label: .semitransparentBadge(EnginePeer(channel).compactDisplayTitle, (data.peer?.nameColor ?? .blue).color), text: "Channel Color", icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
|
||||
let colors = context.peerNameColors.get(data.peer?.nameColor ?? .blue)
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemNameColor, label: .semitransparentBadge(EnginePeer(channel).compactDisplayTitle, colors.main), text: "Channel Color", icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
|
||||
interaction.editingOpenNameColorSetup()
|
||||
}))
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M
|
||||
return string
|
||||
}
|
||||
|
||||
public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseQuoteTintColor: UIColor? = nil, baseQuoteSecondaryTintColor: UIColor? = nil, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false, message: Message?, entityFiles: [MediaId: TelegramMediaFile] = [:], adjustQuoteFontSize: Bool = false) -> NSAttributedString {
|
||||
public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseQuoteTintColor: UIColor? = nil, baseQuoteSecondaryTintColor: UIColor? = nil, baseQuoteTertiaryTintColor: UIColor? = nil, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false, message: Message?, entityFiles: [MediaId: TelegramMediaFile] = [:], adjustQuoteFontSize: Bool = false) -> NSAttributedString {
|
||||
let baseQuoteTintColor = baseQuoteTintColor ?? baseColor
|
||||
|
||||
var nsString: NSString?
|
||||
@ -214,7 +214,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
||||
case .BlockQuote:
|
||||
addFontAttributes(range, .blockQuote)
|
||||
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(title: nil, color: baseQuoteTintColor, secondaryColor: baseQuoteSecondaryTintColor), range: range)
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(title: nil, color: baseQuoteTintColor, secondaryColor: baseQuoteSecondaryTintColor, tertiaryColor: baseQuoteTertiaryTintColor), range: range)
|
||||
case .BankCard:
|
||||
string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range)
|
||||
if underlineLinks && underlineAllLinks {
|
||||
|
||||