Various fixes

This commit is contained in:
Ilya Laktyushin 2023-09-15 22:56:36 +04:00
parent b7e4fb3d57
commit 6f75478d99
13 changed files with 339 additions and 86 deletions

View File

@ -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";

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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] {