mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various fixes
This commit is contained in:
parent
b7e4fb3d57
commit
6f75478d99
@ -9970,3 +9970,36 @@ Sorry for the inconvenience.";
|
||||
"SessionReview.OkAction" = "Got it";
|
||||
|
||||
"WebApp.RemoveAllConfirmationText" = "This will remove **%@** shortcuts from all menus.";
|
||||
|
||||
|
||||
"ChannelBoost.Level" = "Level %@";
|
||||
"ChannelBoost.EnableStories" = "Enable Stories";
|
||||
"ChannelBoost.EnableStoriesText" = "Your channel needs %1$@ to enable posting stories.\n\nAsk your **Premium** subscribers to boost your channel with this link:";
|
||||
|
||||
"ChannelBoost.IncreaseLimit" = "Increase Story Limit";
|
||||
"ChannelBoost.IncreaseLimitText" = "Your channel needs %1$@ to post **%2$@** stories per day.\n\nAsk your **Premium** subscribers to boost your channel with this link:";
|
||||
|
||||
"ChannelBoost.EnableStoriesForChannel" = "Enable Stories for This Channel";
|
||||
"ChannelBoost.EnableStoriesForOtherChannel" = "Enable Stories for The Channel";
|
||||
"ChannelBoost.EnableStoriesForChannelText" = "**%1$@** needs %2$@ to enable posting stories. Help make it possible!";
|
||||
|
||||
"ChannelBoost.EnabledStoriesForChannelText" = "This channel reached **Level 1** and can now post stories.";
|
||||
"ChannelBoost.EnableStoriesMoreRequired" = "This channel needs **%1$@** to enable stories.";
|
||||
|
||||
"ChannelBoost.HelpUpgradeChannel" = "Help Upgrade This Channel";
|
||||
"ChannelBoost.HelpUpgradeChannelText" = "**%1$@** needs %2$@ to be able to post **%3$@** stories per day.";
|
||||
|
||||
"ChannelBoost.YouBoostedChannel" = "You Boosted %1$@!";
|
||||
"ChannelBoost.YouBoostedOtherChannel" = "You Boosted The Channel";
|
||||
|
||||
"ChannelBoost.MoreBoosts_1" = "**%@** more boost";
|
||||
"ChannelBoost.MoreBoosts_any" = "**%@** more boosts";
|
||||
|
||||
"ChannelBoost.CopyLink" = "Copy Link";
|
||||
"ChannelBoost.BoostChannel" = "Boost Channel";
|
||||
|
||||
"Story.TooltipUnmuteVideoSound" = "Tap here to enable sound";
|
||||
|
||||
"Conversation.BoostChannel" = "BOOST";
|
||||
|
||||
"MediaEditor.Audio" = "Audio";
|
||||
|
@ -515,7 +515,8 @@ public class PremiumLimitDisplayComponent: Component {
|
||||
if component.isPremiumDisabled {
|
||||
badgePosition = 0.5
|
||||
}
|
||||
if badgePosition > 1.0 - .ulpOfOne {
|
||||
|
||||
if badgePosition > 1.0 - 0.15 {
|
||||
progressTransition.setFrame(view: self.badgeMaskArrowView, frame: CGRect(origin: CGPoint(x: badgeSize.width - 24.0, y: badgeSize.height - 15.0), size: CGSize(width: 44.0, height: 12.0)))
|
||||
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 1.0, y: 1.0))
|
||||
progressTransition.setFrame(view: self.badgeMaskArrowFillerView, frame: CGRect(x: -12.0, y: -21.0, width: 36.0, height: 24.0))
|
||||
@ -527,7 +528,7 @@ public class PremiumLimitDisplayComponent: Component {
|
||||
} else {
|
||||
self.badgeView.center = CGPoint(x: 3.0 + (size.width - 6.0) * badgePosition + 3.0, y: 82.0)
|
||||
}
|
||||
} else if badgePosition < .ulpOfOne {
|
||||
} else if badgePosition < 0.15 {
|
||||
progressTransition.setFrame(view: self.badgeMaskArrowView, frame: CGRect(origin: CGPoint(x: -20.0, y: badgeSize.height - 15.0), size: CGSize(width: 44.0, height: 12.0)))
|
||||
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.0, y: 1.0))
|
||||
progressTransition.setFrame(view: self.badgeMaskArrowFillerView, frame: CGRect(x: 20.0, y: -21.0, width: 36.0, height: 24.0))
|
||||
@ -581,7 +582,7 @@ public class PremiumLimitDisplayComponent: Component {
|
||||
} else if !self.didPlayAppearanceAnimation || !transition.animation.isImmediate {
|
||||
self.didPlayAppearanceAnimation = true
|
||||
if transition.animation.isImmediate {
|
||||
if component.badgePosition.isZero {
|
||||
if component.badgePosition < 0.1 {
|
||||
self.badgeView.alpha = 1.0
|
||||
if let badgeText = component.badgeText {
|
||||
self.badgeCountLabel.configure(with: badgeText, duration: 0.001)
|
||||
@ -1071,52 +1072,42 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
|
||||
if let _ = link {
|
||||
if let remaining {
|
||||
let valueString: String
|
||||
if remaining == 1 {
|
||||
valueString = "**\(remaining)** more boost"
|
||||
} else {
|
||||
valueString = "**\(remaining)** more boosts"
|
||||
}
|
||||
let valueString = strings.ChannelBoost_MoreBoosts(remaining)
|
||||
if level == 0 {
|
||||
titleText = "Enable Stories"
|
||||
string = "Your channel needs \(valueString) to enable posting stories.\n\nAsk your **Premium** subscribers to boost your channel with this link:"
|
||||
titleText = strings.ChannelBoost_EnableStories
|
||||
string = strings.ChannelBoost_EnableStoriesText(valueString).string
|
||||
} else {
|
||||
titleText = "Increase Story Limit"
|
||||
string = "Your channel needs \(valueString) to post **\(level + 1)** stories per day.\n\nAsk your **Premium** subscribers to boost your channel with this link:"
|
||||
titleText = strings.ChannelBoost_IncreaseLimit
|
||||
string = strings.ChannelBoost_IncreaseLimitText(valueString, "\(level + 1)").string
|
||||
}
|
||||
} else {
|
||||
titleText = "Increase Story Limit"
|
||||
titleText = strings.ChannelBoost_IncreaseLimit
|
||||
string = "Your channel needs **0** more boosts to post **2** stories per day.\n\nAsk your **Premium** subscribers to boost your channel with this link:"
|
||||
}
|
||||
actionButtonText = "Copy Link"
|
||||
actionButtonText = strings.ChannelBoost_CopyLink
|
||||
buttonIconName = "Premium/CopyLink"
|
||||
actionButtonHasGloss = false
|
||||
} else {
|
||||
if let remaining {
|
||||
let valueString: String
|
||||
if remaining == 1 {
|
||||
valueString = "**\(remaining)** more boost"
|
||||
} else {
|
||||
valueString = "**\(remaining)** more boosts"
|
||||
}
|
||||
let valueString = strings.ChannelBoost_MoreBoosts(remaining)
|
||||
if level == 0 {
|
||||
titleText = "Enable Stories for The Channel"
|
||||
string = "**\(peer.compactDisplayTitle)** needs \(valueString) to enable posting stories. Help make it possible!"
|
||||
titleText = isCurrent ? strings.ChannelBoost_EnableStoriesForChannel : strings.ChannelBoost_EnableStoriesForOtherChannel
|
||||
string = strings.ChannelBoost_EnableStoriesForChannelText(peer.compactDisplayTitle, valueString).string
|
||||
} else {
|
||||
titleText = "Help Upgrade Channel"
|
||||
string = "**\(peer.compactDisplayTitle)** needs \(valueString) to be able to post **\(level + 1)** stories per day."
|
||||
titleText = strings.ChannelBoost_HelpUpgradeChannel
|
||||
string = strings.ChannelBoost_HelpUpgradeChannelText(peer.compactDisplayTitle, valueString, "\(level + 1)").string
|
||||
}
|
||||
} else {
|
||||
titleText = "Help Upgrade Channel"
|
||||
titleText = strings.ChannelBoost_HelpUpgradeChannel
|
||||
string = "**\(peer.compactDisplayTitle)** needs **0** more boosts to be able to post **\(level + 1)** stories per day."
|
||||
}
|
||||
actionButtonText = "Boost Channel"
|
||||
actionButtonText = strings.ChannelBoost_BoostChannel
|
||||
buttonIconName = "Premium/BoostChannel"
|
||||
}
|
||||
buttonAnimationName = nil
|
||||
defaultTitle = "Level \(level)"
|
||||
defaultTitle = strings.ChannelBoost_Level("\(level)").string
|
||||
defaultValue = ""
|
||||
premiumValue = "Level \(level + 1)"
|
||||
premiumValue = strings.ChannelBoost_Level("\(level + 1)").string
|
||||
|
||||
premiumTitle = ""
|
||||
|
||||
@ -1125,21 +1116,15 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
actionButtonText = environment.strings.Common_OK
|
||||
|
||||
if let remaining {
|
||||
let valueString: String
|
||||
if remaining == 1 {
|
||||
valueString = "**\(remaining)** more boost"
|
||||
} else {
|
||||
valueString = "**\(remaining)** more boosts"
|
||||
}
|
||||
titleText = isCurrent ? strings.ChannelBoost_YouBoostedChannel(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannel
|
||||
let valueString = strings.ChannelBoost_MoreBoosts(remaining)
|
||||
if level == 0 {
|
||||
titleText = "Enable Stories for The Channel"
|
||||
if remaining == 0 {
|
||||
string = "**You boosted this channel**.\nThis allowed it to post stories."
|
||||
string = strings.ChannelBoost_EnabledStoriesForChannelText
|
||||
} else {
|
||||
string = "**You boosted this channel**.\n\(valueString) needed to enable stories."
|
||||
string = strings.ChannelBoost_EnableStoriesMoreRequired(valueString).string
|
||||
}
|
||||
} else {
|
||||
titleText = "Help Upgrade Channel"
|
||||
if remaining == 0 {
|
||||
string = "**You boosted this channel**.\nThis allowed it to post \(level + 1) stories per day."
|
||||
} else {
|
||||
@ -1203,7 +1188,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
text: .markdown(text: string, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.0
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
@ -1214,7 +1199,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
text: .markdown(text: string, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.0
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
|
@ -19,6 +19,9 @@ import ItemListPeerItem
|
||||
import InviteLinksUI
|
||||
import UndoUI
|
||||
import ShareController
|
||||
import ItemListPeerActionItem
|
||||
|
||||
private let maxUsersDisplayedLimit: Int32 = 50
|
||||
|
||||
private final class ChannelStatsControllerArguments {
|
||||
let context: AccountContext
|
||||
@ -27,14 +30,18 @@ private final class ChannelStatsControllerArguments {
|
||||
let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> Void
|
||||
let copyBoostLink: (String) -> Void
|
||||
let shareBoostLink: (String) -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let expandBoosters: () -> 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) {
|
||||
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) {
|
||||
self.context = context
|
||||
self.loadDetailedGraph = loadDetailedGraph
|
||||
self.openMessageStats = openMessage
|
||||
self.contextAction = contextAction
|
||||
self.copyBoostLink = copyBoostLink
|
||||
self.shareBoostLink = shareBoostLink
|
||||
self.openPeer = openPeer
|
||||
self.expandBoosters = expandBoosters
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,6 +105,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case boostersTitle(PresentationTheme, String)
|
||||
case boostersPlaceholder(PresentationTheme, String)
|
||||
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32)
|
||||
case boostersExpand(PresentationTheme, String)
|
||||
case boostersInfo(PresentationTheme, String)
|
||||
|
||||
case boostLinkTitle(PresentationTheme, String)
|
||||
@ -132,7 +140,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return StatsSection.boostLevel.rawValue
|
||||
case .boostOverviewTitle, .boostOverview:
|
||||
return StatsSection.boostOverview.rawValue
|
||||
case .boostersTitle, .boostersPlaceholder, .booster, .boostersInfo:
|
||||
case .boostersTitle, .boostersPlaceholder, .booster, .boostersExpand, .boostersInfo:
|
||||
return StatsSection.boosters.rawValue
|
||||
case .boostLinkTitle, .boostLink, .boostLinkInfo:
|
||||
return StatsSection.boostLink.rawValue
|
||||
@ -197,14 +205,16 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 2004
|
||||
case let .booster(index, _, _, _, _):
|
||||
return 2005 + index
|
||||
case .boostersInfo:
|
||||
case .boostersExpand:
|
||||
return 10000
|
||||
case .boostLinkTitle:
|
||||
case .boostersInfo:
|
||||
return 10001
|
||||
case .boostLink:
|
||||
case .boostLinkTitle:
|
||||
return 10002
|
||||
case .boostLinkInfo:
|
||||
case .boostLink:
|
||||
return 10003
|
||||
case .boostLinkInfo:
|
||||
return 10004
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,6 +388,12 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostersExpand(lhsTheme, lhsText):
|
||||
if case let .boostersExpand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostersInfo(lhsTheme, lhsText):
|
||||
if case let .boostersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -459,11 +475,15 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case let .booster(_, _, dateTimeFormat, peer, expires):
|
||||
let expiresValue = stringForFullDate(timestamp: expires, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text("Boost expires on \(expiresValue)", .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: {
|
||||
|
||||
arguments.openPeer(peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
case let .boostersExpand(theme, title):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandBoosters()
|
||||
})
|
||||
case let .boostLevel(_, count, level, position):
|
||||
let inactiveText = "Level \(level)"
|
||||
let activeText = "Level \(level + 1)"
|
||||
let inactiveText = presentationData.strings.ChannelBoost_Level("\(level)").string
|
||||
let activeText = presentationData.strings.ChannelBoost_Level("\(level + 1)").string
|
||||
return BoostLevelHeaderItem(theme: presentationData.theme, count: count, position: position, activeText: activeText, inactiveText: inactiveText, sectionId: self.section)
|
||||
case let .boostOverview(_, stats):
|
||||
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
|
||||
@ -487,24 +507,34 @@ private struct ChannelStatsControllerState: Equatable {
|
||||
}
|
||||
|
||||
let section: Section
|
||||
let boostersExpanded: Bool
|
||||
|
||||
init() {
|
||||
self.section = .stats
|
||||
self.boostersExpanded = false
|
||||
}
|
||||
|
||||
init(section: Section) {
|
||||
init(section: Section, boostersExpanded: Bool) {
|
||||
self.section = section
|
||||
self.boostersExpanded = boostersExpanded
|
||||
}
|
||||
|
||||
static func ==(lhs: ChannelStatsControllerState, rhs: ChannelStatsControllerState) -> Bool {
|
||||
if lhs.section != rhs.section {
|
||||
return false
|
||||
}
|
||||
if lhs.boostersExpanded != rhs.boostersExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func withUpdatedSection(_ section: Section) -> ChannelStatsControllerState {
|
||||
return ChannelStatsControllerState(section: section)
|
||||
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded)
|
||||
}
|
||||
|
||||
func withUpdatedBoostersExpanded(_ boostersExpanded: Bool) -> ChannelStatsControllerState {
|
||||
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,7 +615,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
} else {
|
||||
progress = 1.0
|
||||
}
|
||||
entries.append(.boostLevel(presentationData.theme, Int32(boostData.level), Int32(boostData.boosts), progress))
|
||||
entries.append(.boostLevel(presentationData.theme, Int32(boostData.boosts), Int32(boostData.level), progress))
|
||||
|
||||
entries.append(.boostOverviewTitle(presentationData.theme, "OVERVIEW"))
|
||||
entries.append(.boostOverview(presentationData.theme, boostData))
|
||||
@ -610,10 +640,23 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
|
||||
if let boostersState {
|
||||
var boosterIndex: Int32 = 0
|
||||
for booster in boostersState.boosters {
|
||||
|
||||
var boosters: [ChannelBoostersContext.State.Booster] = boostersState.boosters
|
||||
var effectiveExpanded = state.boostersExpanded
|
||||
if boosters.count > maxUsersDisplayedLimit && !state.boostersExpanded {
|
||||
boosters = Array(boosters.prefix(Int(maxUsersDisplayedLimit)))
|
||||
} else {
|
||||
effectiveExpanded = true
|
||||
}
|
||||
|
||||
for booster in boosters {
|
||||
entries.append(.booster(boosterIndex, presentationData.theme, presentationData.dateTimeFormat, booster.peer, booster.expires))
|
||||
boosterIndex += 1
|
||||
}
|
||||
|
||||
if !effectiveExpanded {
|
||||
entries.append(.boostersExpand(presentationData.theme, presentationData.strings.PeopleNearby_ShowMorePeople(Int32(boostersState.count) - maxUsersDisplayedLimit)))
|
||||
}
|
||||
}
|
||||
|
||||
if let boostersFooter {
|
||||
@ -678,6 +721,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
let boostersContext = ChannelBoostersContext(account: context.account, peerId: peerId)
|
||||
|
||||
var presentImpl: ((ViewController) -> Void)?
|
||||
var navigateToProfileImpl: ((EnginePeer) -> Void)?
|
||||
|
||||
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
|
||||
return statsContext.loadDetailedGraph(graph, x: x)
|
||||
@ -733,6 +777,12 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }))
|
||||
}
|
||||
presentImpl?(shareController)
|
||||
},
|
||||
openPeer: { peer in
|
||||
navigateToProfileImpl?(peer)
|
||||
},
|
||||
expandBoosters: {
|
||||
updateState { $0.withUpdatedBoostersExpanded(true) }
|
||||
})
|
||||
|
||||
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
||||
@ -802,6 +852,12 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
}
|
||||
})
|
||||
}
|
||||
controller.visibleBottomContentOffsetChanged = { offset in
|
||||
let state = stateValue.with { $0 }
|
||||
if case let .known(value) = offset, value < 100.0, case .boosts = state.section, state.boostersExpanded {
|
||||
boostersContext.loadMore()
|
||||
}
|
||||
}
|
||||
controller.titleControlValueChanged = { value in
|
||||
updateState { $0.withUpdatedSection(value == 1 ? .boosts : .stats) }
|
||||
}
|
||||
@ -840,6 +896,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
presentImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -180,6 +180,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
||||
case displayStoryReactionTooltip = 46
|
||||
case storyStealthModeReplyCount = 47
|
||||
case viewOnceTooltip = 48
|
||||
case displayStoryUnmuteTooltip = 49
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
@ -429,6 +430,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func viewOnceTooltip() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.viewOnceTooltip.key)
|
||||
}
|
||||
|
||||
static func displayStoryUnmuteTooltip() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryUnmuteTooltip.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
@ -1633,4 +1638,25 @@ public struct ApplicationSpecificNotice {
|
||||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func setDisplayStoryUnmuteTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.displayStoryUnmuteTooltip(), entry)
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public static func displayStoryUnmuteTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayStoryUnmuteTooltip())
|
||||
|> map { view -> Bool in
|
||||
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,9 @@ public final class AudioWaveformComponent: Component {
|
||||
var foregroundColor: UIColor
|
||||
}
|
||||
|
||||
public final class CloneLayer: SimpleLayer {
|
||||
}
|
||||
|
||||
private final class LayerImpl: SimpleLayer {
|
||||
private var shimmerNode: ShimmerEffectNode?
|
||||
private var shimmerMask: SimpleLayer?
|
||||
@ -158,6 +161,22 @@ public final class AudioWaveformComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
weak var cloneLayer: CloneLayer? {
|
||||
didSet {
|
||||
if let cloneLayer = self.cloneLayer {
|
||||
cloneLayer.contents = self.contents
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public var contents: Any? {
|
||||
didSet {
|
||||
if let cloneLayer = self.cloneLayer {
|
||||
cloneLayer.contents = self.contents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public static var layerClass: AnyClass {
|
||||
@ -224,6 +243,12 @@ public final class AudioWaveformComponent: Component {
|
||||
self.statusDisposable?.dispose()
|
||||
}
|
||||
|
||||
public var cloneLayer: CloneLayer? {
|
||||
didSet {
|
||||
(self.layer as! LayerImpl).cloneLayer = self.cloneLayer
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
var location = recognizer.location(in: self)
|
||||
location.x -= self.bounds.minX
|
||||
@ -392,7 +417,7 @@ public final class AudioWaveformComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override public func draw(_ rect: CGRect) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
|
@ -592,11 +592,20 @@ public final class MediaEditor {
|
||||
|
||||
self.setupTimeObservers()
|
||||
Queue.mainQueue().justDispatch {
|
||||
player.playImmediately(atRate: 1.0)
|
||||
additionalPlayer?.playImmediately(atRate: 1.0)
|
||||
self.audioPlayer?.playImmediately(atRate: 1.0)
|
||||
self.onPlaybackAction(.play)
|
||||
self.volumeFade = self.player?.fadeVolume(from: 0.0, to: 1.0, duration: 0.4)
|
||||
let startPlayback = {
|
||||
player.playImmediately(atRate: 1.0)
|
||||
additionalPlayer?.playImmediately(atRate: 1.0)
|
||||
self.audioPlayer?.playImmediately(atRate: 1.0)
|
||||
self.onPlaybackAction(.play)
|
||||
self.volumeFade = self.player?.fadeVolume(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
if let audioPlayer = self.audioPlayer, audioPlayer.status != .readyToPlay {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
startPlayback()
|
||||
}
|
||||
} else {
|
||||
startPlayback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1099,6 +1108,9 @@ public final class MediaEditor {
|
||||
}
|
||||
self.audioPlayer?.currentItem?.forwardPlaybackEndTime = CMTime(seconds: offset + upperBound, preferredTimescale: CMTimeScale(1000))
|
||||
self.audioPlayer?.seek(to: audioTime, toleranceBefore: .zero, toleranceAfter: .zero)
|
||||
if !self.sourceIsVideo {
|
||||
self.audioPlayer?.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1330,8 +1330,12 @@ final class MediaEditorScreenComponent: Component {
|
||||
if let mediaEditor {
|
||||
mediaEditor.setAudioTrackTrimRange(start..<end, apply: done)
|
||||
if isAudioOnly {
|
||||
let offset = (mediaEditor.values.audioTrackOffset ?? 0.0)
|
||||
if done {
|
||||
mediaEditor.seek(start, andPlay: true)
|
||||
mediaEditor.seek(offset + start, andPlay: true)
|
||||
} else {
|
||||
mediaEditor.seek(offset + start, andPlay: false)
|
||||
mediaEditor.stop()
|
||||
}
|
||||
} else {
|
||||
if done {
|
||||
@ -1346,8 +1350,13 @@ final class MediaEditorScreenComponent: Component {
|
||||
if let mediaEditor {
|
||||
mediaEditor.setAudioTrackOffset(offset, apply: done)
|
||||
if done {
|
||||
mediaEditor.play()
|
||||
if !isAudioOnly {
|
||||
mediaEditor.play()
|
||||
}
|
||||
} else {
|
||||
if isAudioOnly {
|
||||
mediaEditor.seek(offset + (mediaEditor.values.audioTrackTrimRange?.lowerBound ?? 0.0), andPlay: false)
|
||||
}
|
||||
mediaEditor.stop()
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +166,7 @@ final class VideoScrubberComponent: Component {
|
||||
private let audioTitle = ComponentView<Empty>()
|
||||
|
||||
private let audioWaveform = ComponentView<Empty>()
|
||||
private let waveformCloneLayer = AudioWaveformComponent.View.CloneLayer()
|
||||
|
||||
private let trimView = TrimView(frame: .zero)
|
||||
private let ghostTrimView = TrimView(frame: .zero)
|
||||
@ -223,6 +224,8 @@ final class VideoScrubberComponent: Component {
|
||||
|
||||
self.audioIconView = UIImageView(image: UIImage(bundleImageName: "Media Editor/SmallAudio"))
|
||||
|
||||
self.waveformCloneLayer.opacity = 0.3
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = false
|
||||
@ -343,6 +346,12 @@ final class VideoScrubberComponent: Component {
|
||||
component.audioOffsetUpdated(offset, done)
|
||||
}
|
||||
|
||||
var isDragging = false
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.isDragging = true
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.updateAudioOffset(done: false)
|
||||
}
|
||||
@ -350,11 +359,15 @@ final class VideoScrubberComponent: Component {
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if !decelerate {
|
||||
self.updateAudioOffset(done: true)
|
||||
self.isDragging = false
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
self.updateAudioOffset(done: true)
|
||||
self.isDragging = false
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
}
|
||||
|
||||
@objc private func longPressed(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
@ -397,7 +410,10 @@ final class VideoScrubberComponent: Component {
|
||||
let length = end - start
|
||||
let fraction = (location.x - start) / length
|
||||
|
||||
let position = max(component.startPosition, min(component.endPosition, trimDuration * fraction))
|
||||
var position = max(component.startPosition, min(component.endPosition, trimDuration * fraction))
|
||||
if component.audioOnly, let offset = component.audioData?.offset {
|
||||
position += offset
|
||||
}
|
||||
let transition: Transition = .immediate
|
||||
switch gestureRecognizer.state {
|
||||
case .began, .changed:
|
||||
@ -443,6 +459,10 @@ final class VideoScrubberComponent: Component {
|
||||
|
||||
let updatedPosition: Double
|
||||
if let (start, from, to, _) = self.positionAnimation {
|
||||
var from = from
|
||||
if let offset = component.audioData?.offset {
|
||||
from -= offset
|
||||
}
|
||||
let duration = to - from
|
||||
let fraction = duration > 0.0 ? (timestamp - start) / duration : 0.0
|
||||
updatedPosition = max(component.startPosition, min(component.endPosition, from + (to - from) * fraction))
|
||||
@ -450,8 +470,12 @@ final class VideoScrubberComponent: Component {
|
||||
self.positionAnimation = (start, from, to, true)
|
||||
}
|
||||
} else {
|
||||
var position = component.position
|
||||
if let offset = component.audioData?.offset {
|
||||
position -= offset
|
||||
}
|
||||
let advance = component.isPlaying ? timestamp - component.generationTimestamp : 0.0
|
||||
updatedPosition = max(component.startPosition, min(component.endPosition, component.position + advance))
|
||||
updatedPosition = max(component.startPosition, min(component.endPosition, position + advance))
|
||||
}
|
||||
let cursorHeight: CGFloat = component.audioData != nil ? 80.0 : 50.0
|
||||
self.cursorView.frame = cursorFrame(size: scrubberSize, height: cursorHeight, position: updatedPosition, duration: trimDuration)
|
||||
@ -588,7 +612,8 @@ final class VideoScrubberComponent: Component {
|
||||
components.append(title)
|
||||
}
|
||||
if components.isEmpty {
|
||||
components.append("Audio")
|
||||
let strings = component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
components.append(strings.MediaEditor_Audio)
|
||||
}
|
||||
trackTitle = components.joined(separator: " • ")
|
||||
}
|
||||
@ -635,16 +660,26 @@ final class VideoScrubberComponent: Component {
|
||||
if let audioData = component.audioData {
|
||||
let samples = audioData.samples ?? Data()
|
||||
|
||||
if let view = self.audioWaveform.view, previousComponent?.audioData?.samples == nil && audioData.samples != nil, let snapshotView = view.snapshotContentTree() {
|
||||
if let view = self.audioWaveform.view, previousComponent?.audioData?.samples == nil && audioData.samples != nil, let vibrancySnapshotView = view.snapshotContentTree(), let snapshotView = self.waveformCloneLayer.snapshotContentTreeAsView() {
|
||||
vibrancySnapshotView.frame = view.frame
|
||||
snapshotView.alpha = 0.3
|
||||
snapshotView.frame = view.frame
|
||||
self.audioVibrancyContainer.addSubview(snapshotView)
|
||||
self.audioVibrancyContainer.addSubview(vibrancySnapshotView)
|
||||
self.audioContainerView.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
snapshotView.removeFromSuperview()
|
||||
vibrancySnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
vibrancySnapshotView.removeFromSuperview()
|
||||
})
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 0.3, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
vibrancySnapshotView.removeFromSuperview()
|
||||
})
|
||||
|
||||
view.layer.animateScaleY(from: 0.01, to: 1.0, duration: 0.2)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
self.waveformCloneLayer.animateScaleY(from: 0.01, to: 1.0, duration: 0.2)
|
||||
self.waveformCloneLayer.animateAlpha(from: 0.0, to: 0.3, duration: 0.2)
|
||||
}
|
||||
let audioWaveformSize = self.audioWaveform.update(
|
||||
transition: transition,
|
||||
@ -664,11 +699,15 @@ final class VideoScrubberComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: audioContainerFrame.width, height: scrubberHeight)
|
||||
)
|
||||
if let view = self.audioWaveform.view {
|
||||
if let view = self.audioWaveform.view as? AudioWaveformComponent.View {
|
||||
if view.superview == nil {
|
||||
view.cloneLayer = self.waveformCloneLayer
|
||||
self.audioVibrancyContainer.addSubview(view)
|
||||
self.audioContainerView.layer.addSublayer(self.waveformCloneLayer)
|
||||
}
|
||||
audioTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isAudioSelected || component.audioOnly ? 0.0 : 6.0), size: audioWaveformSize))
|
||||
|
||||
audioTransition.setFrame(layer: self.waveformCloneLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isAudioSelected || component.audioOnly ? 0.0 : 6.0), size: audioWaveformSize))
|
||||
}
|
||||
}
|
||||
|
||||
@ -746,16 +785,23 @@ final class VideoScrubberComponent: Component {
|
||||
containerRightEdge = ghostRightHandleFrame.minX
|
||||
}
|
||||
|
||||
transition.setAlpha(view: self.cursorView, alpha: self.trimView.isPanningTrimHandle || self.ghostTrimView.isPanningTrimHandle ? 0.0 : 1.0)
|
||||
let isDraggingAudio = self.isDragging && component.audioOnly
|
||||
let isCursorHidden = isDraggingAudio || self.trimView.isPanningTrimHandle || self.ghostTrimView.isPanningTrimHandle
|
||||
var cursorTransition = transition
|
||||
if isCursorHidden {
|
||||
cursorTransition = .immediate
|
||||
}
|
||||
cursorTransition.setAlpha(view: self.cursorView, alpha: isCursorHidden ? 0.0 : 1.0)
|
||||
|
||||
if self.isPanningPositionHandle || !component.isPlaying {
|
||||
self.positionAnimation = nil
|
||||
self.displayLink?.isPaused = true
|
||||
|
||||
let cursorHeight: CGFloat = component.audioData != nil ? 80.0 : 50.0
|
||||
let cursorPosition = component.position
|
||||
// if self.cursorView.alpha.isZero {
|
||||
// cursorPosition = component.startPosition
|
||||
// }
|
||||
var cursorPosition = component.position
|
||||
if component.audioOnly, let audioOffset = component.audioData?.offset {
|
||||
cursorPosition -= audioOffset
|
||||
}
|
||||
videoTransition.setFrame(view: self.cursorView, frame: cursorFrame(size: scrubberSize, height: cursorHeight, position: cursorPosition, duration: trimDuration))
|
||||
} else {
|
||||
if let (_, _, end, ended) = self.positionAnimation {
|
||||
|
@ -435,9 +435,6 @@ public final class MessageInputPanelComponent: Component {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.fieldBackgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.5), enableBlur: true)
|
||||
if sharedIsReduceTransparencyEnabled {
|
||||
|
||||
}
|
||||
|
||||
let style: UIBlurEffect.Style = .dark
|
||||
let blurEffect = UIBlurEffect(style: style)
|
||||
|
@ -2513,9 +2513,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
#endif*/
|
||||
}
|
||||
|
||||
var isFirstItem = false
|
||||
var itemChanged = false
|
||||
var resetInputContents: MessageInputPanelComponent.SendMessageInput?
|
||||
if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id {
|
||||
isFirstItem = self.component == nil
|
||||
itemChanged = self.component != nil
|
||||
self.initializedOffset = false
|
||||
|
||||
@ -3724,6 +3726,16 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.currentSoundButtonState = soundButtonState
|
||||
}
|
||||
|
||||
if isVideo && !isSilentVideo && component.isAudioMuted && (isFirstItem || itemChanged) {
|
||||
if isFirstItem {
|
||||
Queue.mainQueue().after(0.4) {
|
||||
self.maybeDisplayUnmuteVideoTooltip()
|
||||
}
|
||||
} else {
|
||||
self.maybeDisplayUnmuteVideoTooltip()
|
||||
}
|
||||
}
|
||||
|
||||
let storyPrivacyIcon: StoryPrivacyIconComponent.Privacy?
|
||||
if case .user = component.slice.peer {
|
||||
if component.slice.item.storyItem.isCloseFriends {
|
||||
@ -6333,6 +6345,26 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
component.controller()?.present(tooltipScreen, in: .current)
|
||||
}
|
||||
|
||||
func maybeDisplayUnmuteVideoTooltip() {
|
||||
guard let component = self.component, component.visibilityFraction == 1.0 else {
|
||||
return
|
||||
}
|
||||
guard let soundButtonView = self.soundButton.view else {
|
||||
return
|
||||
}
|
||||
|
||||
let tooltipScreen = TooltipScreen(
|
||||
account: component.context.account,
|
||||
sharedContext: component.context.sharedContext,
|
||||
text: .plain(text: component.strings.Story_TooltipUnmuteVideoSound), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: nil).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .default, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
}
|
||||
)
|
||||
component.controller()?.present(tooltipScreen, in: .current)
|
||||
|
||||
let _ = ApplicationSpecificNotice.setDisplayStoryUnmuteTooltip(accountManager: component.context.sharedContext.accountManager).start()
|
||||
}
|
||||
|
||||
func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
guard let component = self.component, case .compact = component.metrics.widthClass else {
|
||||
return
|
||||
|
@ -339,6 +339,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
subtitle = nil
|
||||
}
|
||||
actionTitle = item.presentationData.strings.Chat_OpenStory
|
||||
case "telegram_channel_boost":
|
||||
actionTitle = item.presentationData.strings.Conversation_BoostChannel
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -842,9 +842,9 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
return
|
||||
}
|
||||
|
||||
var boosted = false
|
||||
var isBoosted = false
|
||||
if case let .error(error) = canApplyStatus, case .peerBoostAlreadyActive = error {
|
||||
boosted = true
|
||||
isBoosted = true
|
||||
}
|
||||
|
||||
var isCurrent = false
|
||||
@ -852,14 +852,24 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
isCurrent = true
|
||||
}
|
||||
|
||||
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: nil, boosted: boosted)
|
||||
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: nil, boosted: true)
|
||||
var currentLevel = Int32(status.level)
|
||||
var currentLevelBoosts = Int32(status.currentLevelBoosts)
|
||||
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
|
||||
|
||||
if isBoosted && status.boosts == currentLevelBoosts {
|
||||
currentLevel = max(0, currentLevel - 1)
|
||||
nextLevelBoosts = currentLevelBoosts
|
||||
currentLevelBoosts = max(0, currentLevelBoosts - 1)
|
||||
}
|
||||
|
||||
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, boosted: isBoosted)
|
||||
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, boosted: true)
|
||||
let nextCount = Int32(status.boosts + 1)
|
||||
|
||||
var updateImpl: (() -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), action: {
|
||||
if boosted {
|
||||
if isBoosted {
|
||||
return true
|
||||
}
|
||||
var dismiss = false
|
||||
|
@ -260,6 +260,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
fileprivate var webView: WebAppWebView?
|
||||
private var placeholderIcon: (UIImage, Bool)?
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
|
||||
private var scheduledBackgroundColor: UIColor?
|
||||
|
||||
fileprivate let loadingProgressPromise = Promise<CGFloat?>(nil)
|
||||
|
||||
@ -528,6 +530,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear)
|
||||
transition.updateAlpha(layer: webView.layer, alpha: 1.0)
|
||||
|
||||
if let color = self.scheduledBackgroundColor {
|
||||
self.scheduledBackgroundColor = nil
|
||||
transition.updateBackgroundColor(node: self.backgroundNode, color: color)
|
||||
}
|
||||
self.updateHeaderBackgroundColor(transition: transition)
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
transition.updateAlpha(node: placeholderNode, alpha: 0.0, completion: { [weak placeholderNode] _ in
|
||||
@ -867,8 +876,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
case "web_app_set_background_color":
|
||||
if let json = json, let colorValue = json["color"] as? String, let color = UIColor(hexString: colorValue) {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear)
|
||||
transition.updateBackgroundColor(node: self.backgroundNode, color: color)
|
||||
if self.placeholderNode != nil {
|
||||
self.scheduledBackgroundColor = color
|
||||
} else {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear)
|
||||
transition.updateBackgroundColor(node: self.backgroundNode, color: color)
|
||||
}
|
||||
}
|
||||
case "web_app_set_header_color":
|
||||
if let json = json {
|
||||
@ -879,7 +892,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.headerColor = color
|
||||
self.headerColorKey = nil
|
||||
}
|
||||
self.updateHeaderBackgroundColor(transition: .animated(duration: 0.2, curve: .linear))
|
||||
if self.placeholderNode == nil {
|
||||
self.updateHeaderBackgroundColor(transition: .animated(duration: 0.2, curve: .linear))
|
||||
}
|
||||
}
|
||||
case "web_app_open_popup":
|
||||
if let json = json, let message = json["message"] as? String, let buttons = json["buttons"] as? [Any] {
|
||||
|
Loading…
x
Reference in New Issue
Block a user