Various improvements

This commit is contained in:
Ilya Laktyushin 2023-06-29 06:39:59 +02:00
parent 1387bb5416
commit 7b0bbd0cce
30 changed files with 1105 additions and 654 deletions

View File

@ -2356,7 +2356,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize())
parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: .animation(name: "ChatListFoldersTooltip", delay: 0.6), location: .point(location, .bottom), shouldDismissOnTouch: { point in parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: .animation(name: "ChatListFoldersTooltip", delay: 0.6, tintColor: nil), location: .point(location, .bottom), shouldDismissOnTouch: { point in
guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else { guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else {
return .dismiss(consume: false) return .dismiss(consume: false)
} }

View File

@ -128,16 +128,23 @@ public final class LottieAnimationComponent: Component {
} }
public func playOnce() { public func playOnce() {
guard let animationView = self.animationView else { guard let animationView = self.animationView, let component = self.component else {
return return
} }
animationView.stop() animationView.stop()
animationView.loopMode = .playOnce animationView.loopMode = .playOnce
if let range = component.animation.range {
animationView.play(fromProgress: range.0, toProgress: range.1, completion: { [weak self] _ in
self?.currentCompletion?()
})
} else {
animationView.play { [weak self] _ in animationView.play { [weak self] _ in
self?.currentCompletion?() self?.currentCompletion?()
} }
} }
}
func update(component: LottieAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize { func update(component: LottieAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize {
var updatePlayback = false var updatePlayback = false

View File

@ -485,6 +485,7 @@ private let color6Tag = GenericComponentViewTag()
private let color7Tag = GenericComponentViewTag() private let color7Tag = GenericComponentViewTag()
private let color8Tag = GenericComponentViewTag() private let color8Tag = GenericComponentViewTag()
private let colorTags = [color1Tag, color2Tag, color3Tag, color4Tag, color5Tag, color6Tag, color7Tag, color8Tag] private let colorTags = [color1Tag, color2Tag, color3Tag, color4Tag, color5Tag, color6Tag, color7Tag, color8Tag]
private let cancelButtonTag = GenericComponentViewTag()
private let doneButtonTag = GenericComponentViewTag() private let doneButtonTag = GenericComponentViewTag()
private final class DrawingScreenComponent: CombinedComponent { private final class DrawingScreenComponent: CombinedComponent {
@ -2074,6 +2075,9 @@ private final class DrawingScreenComponent: CombinedComponent {
animatingOut = true animatingOut = true
} }
if animatingOut && component.sourceHint == .storyEditor {
} else {
let backButton = backButton.update( let backButton = backButton.update(
component: Button( component: Button(
content: AnyComponent( content: AnyComponent(
@ -2094,7 +2098,7 @@ private final class DrawingScreenComponent: CombinedComponent {
dismiss.invoke(Void()) dismiss.invoke(Void())
} }
} }
).minSize(CGSize(width: 44.0, height: 44.0)), ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(cancelButtonTag),
availableSize: CGSize(width: 33.0, height: 33.0), availableSize: CGSize(width: 33.0, height: 33.0),
transition: .immediate transition: .immediate
) )
@ -2110,6 +2114,7 @@ private final class DrawingScreenComponent: CombinedComponent {
.position(backButtonPosition) .position(backButtonPosition)
.opacity(controlsAreVisible ? 1.0 : 0.0) .opacity(controlsAreVisible ? 1.0 : 0.0)
) )
}
return context.availableSize return context.availableSize
} }
@ -2497,6 +2502,9 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
} }
if let view = self.componentHost.findTaggedView(tag: bottomGradientTag) { if let view = self.componentHost.findTaggedView(tag: bottomGradientTag) {
if self.controller?.sourceHint == .storyEditor {
view.isHidden = true
}
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
} }
if let buttonView = self.componentHost.findTaggedView(tag: undoButtonTag) { if let buttonView = self.componentHost.findTaggedView(tag: undoButtonTag) {

View File

@ -206,7 +206,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .addException(title): case let .addException(title):
let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme) let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme)
return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: title, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: {
arguments.openAddException() arguments.openAddException()
}) })
case let .exceptionItem(_, peer, label): case let .exceptionItem(_, peer, label):

View File

@ -1958,10 +1958,6 @@ public class CameraScreen: ViewController {
self.navigationPresentation = .flatModal self.navigationPresentation = .flatModal
self.requestAudioSession() self.requestAudioSession()
if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true)
}
} }
required public init(coder: NSCoder) { required public init(coder: NSCoder) {
@ -1982,7 +1978,11 @@ public class CameraScreen: ViewController {
} }
private func requestAudioSession() { private func requestAudioSession() {
self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { _ in }, deactivate: { _ in self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { _ in
if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true)
}
}, deactivate: { _ in
return .single(Void()) return .single(Void())
}) })
} }

View File

@ -32,6 +32,7 @@ swift_library(
"//submodules/Components/PagerComponent:PagerComponent", "//submodules/Components/PagerComponent:PagerComponent",
"//submodules/PremiumUI", "//submodules/PremiumUI",
"//submodules/ProgressNavigationButtonNode", "//submodules/ProgressNavigationButtonNode",
"//submodules/TelegramUI/Components/SwitchComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -16,72 +16,7 @@ import EmojiStatusComponent
import PremiumUI import PremiumUI
import ProgressNavigationButtonNode import ProgressNavigationButtonNode
import Postbox import Postbox
import SwitchComponent
private final class SwitchComponent: Component {
typealias EnvironmentType = Empty
let value: Bool
let valueUpdated: (Bool) -> Void
init(
value: Bool,
valueUpdated: @escaping (Bool) -> Void
) {
self.value = value
self.valueUpdated = valueUpdated
}
static func ==(lhs: SwitchComponent, rhs: SwitchComponent) -> Bool {
if lhs.value != rhs.value {
return false
}
return true
}
final class View: UIView {
private let switchView: UISwitch
private var component: SwitchComponent?
override init(frame: CGRect) {
self.switchView = UISwitch()
super.init(frame: frame)
self.addSubview(self.switchView)
self.switchView.addTarget(self, action: #selector(self.valueChanged(_:)), for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func valueChanged(_ sender: Any) {
self.component?.valueUpdated(self.switchView.isOn)
}
func update(component: SwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.component = component
self.switchView.setOn(component.value, animated: !transition.animation.isImmediate)
self.switchView.sizeToFit()
self.switchView.frame = CGRect(origin: .zero, size: self.switchView.frame.size)
return self.switchView.frame.size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class TitleFieldComponent: Component { private final class TitleFieldComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty

View File

@ -38,5 +38,10 @@ fragment half4 dualFragmentShader(RasterizerData in [[stage_in]],
float side = 1.0 * aspectRatio; float side = 1.0 * aspectRatio;
float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, roundness)), side * roundness)); float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, roundness)), side * roundness));
if (roundness > 0.0) {
return half4(in.localPos.x, 0.0, 0.0, 1.0);
} else {
return mix(half4(color, 0.0), half4(color, 1.0 * alpha), distance); return mix(half4(color, 0.0), half4(color, 1.0 * alpha), distance);
}
} }

View File

@ -9,25 +9,27 @@ import AccountContext
public struct MediaEditorResultPrivacy: Codable, Equatable { public struct MediaEditorResultPrivacy: Codable, Equatable {
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case type
case privacy case privacy
case peers
case timeout case timeout
case disableForwarding
case archive case archive
} }
public let privacy: EngineStoryPrivacy public let privacy: EngineStoryPrivacy
public let timeout: Int public let timeout: Int
public let archive: Bool public let isForwardingDisabled: Bool
public let pin: Bool
public init( public init(
privacy: EngineStoryPrivacy, privacy: EngineStoryPrivacy,
timeout: Int, timeout: Int,
archive: Bool isForwardingDisabled: Bool,
pin: Bool
) { ) {
self.privacy = privacy self.privacy = privacy
self.timeout = timeout self.timeout = timeout
self.archive = archive self.isForwardingDisabled = isForwardingDisabled
self.pin = pin
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -35,7 +37,8 @@ public struct MediaEditorResultPrivacy: Codable, Equatable {
self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy) self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy)
self.timeout = Int(try container.decode(Int32.self, forKey: .timeout)) self.timeout = Int(try container.decode(Int32.self, forKey: .timeout))
self.archive = try container.decode(Bool.self, forKey: .archive) self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .disableForwarding) ?? false
self.pin = try container.decode(Bool.self, forKey: .archive)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -43,7 +46,8 @@ public struct MediaEditorResultPrivacy: Codable, Equatable {
try container.encode(self.privacy, forKey: .privacy) try container.encode(self.privacy, forKey: .privacy)
try container.encode(Int32(self.timeout), forKey: .timeout) try container.encode(Int32(self.timeout), forKey: .timeout)
try container.encode(self.archive, forKey: .archive) try container.encode(self.isForwardingDisabled, forKey: .disableForwarding)
try container.encode(self.pin, forKey: .archive)
} }
} }

View File

@ -342,8 +342,8 @@ private func verticesData(
} }
let relativeSize = CGSize( let relativeSize = CGSize(
width: size.width / containerSize.width, width: size.width / containerSize.width * 2.0,
height: size.height / containerSize.height height: size.height / containerSize.height * 2.0
) )
let relativeOffset = CGPoint( let relativeOffset = CGPoint(
x: position.x / containerSize.width, x: position.x / containerSize.width,
@ -358,24 +358,45 @@ private func verticesData(
size: relativeSize size: relativeSize
) )
let cosAngle = Float(cos(rotation))
let sinAngle = Float(sin(rotation))
let centerX = Float(rect.midX)
let centerY = Float(rect.midY)
let halfWidth = Float(rect.width) / 2.0
let halfHeight = Float(rect.height) / 2.0
let x1 = centerX + (halfWidth * cosAngle) - (halfHeight * sinAngle)
let y1 = centerY + (halfWidth * sinAngle) + (halfHeight * cosAngle)
let x2 = centerX - (halfWidth * cosAngle) - (halfHeight * sinAngle)
let y2 = centerY - (halfWidth * sinAngle) + (halfHeight * cosAngle)
let x3 = centerX + (halfWidth * cosAngle) + (halfHeight * sinAngle)
let y3 = centerY + (halfWidth * sinAngle) - (halfHeight * cosAngle)
let x4 = centerX - (halfWidth * cosAngle) + (halfHeight * sinAngle)
let y4 = centerY - (halfWidth * sinAngle) - (halfHeight * cosAngle)
return [ return [
VertexData( VertexData(
pos: simd_float4(x: Float(rect.minX) * 2.0, y: Float(rect.minY) * 2.0, z: z, w: 1), pos: simd_float4(x: x1, y: y1, z: z, w: 1),
texCoord: topLeft, texCoord: topLeft,
localPos: simd_float2(0.0, 0.0) localPos: simd_float2(0.0, 0.0)
), ),
VertexData( VertexData(
pos: simd_float4(x: Float(rect.maxX) * 2.0, y: Float(rect.minY) * 2.0, z: z, w: 1), pos: simd_float4(x: x2, y: y2, z: z, w: 1),
texCoord: topRight, texCoord: topRight,
localPos: simd_float2(1.0, 0.0) localPos: simd_float2(1.0, 0.0)
), ),
VertexData( VertexData(
pos: simd_float4(x: Float(rect.minX) * 2.0, y: Float(rect.maxY) * 2.0, z: z, w: 1), pos: simd_float4(x: x3, y: y3, z: z, w: 1),
texCoord: bottomLeft, texCoord: bottomLeft,
localPos: simd_float2(0.0, 1.0) localPos: simd_float2(0.0, 1.0)
), ),
VertexData( VertexData(
pos: simd_float4(x: Float(rect.maxX) * 2.0, y: Float(rect.maxY) * 2.0, z: z, w: 1), pos: simd_float4(x: x4, y: y4, z: z, w: 1),
texCoord: bottomRight, texCoord: bottomRight,
localPos: simd_float2(1.0, 1.0) localPos: simd_float2(1.0, 1.0)
), ),

View File

@ -562,6 +562,10 @@ final class MediaEditorScreenComponent: Component {
view.alpha = 1.0 view.alpha = 1.0
} }
if let buttonView = self.cancelButton.view as? Button.View, let view = buttonView.content as? LottieAnimationComponent.View {
view.playOnce()
}
let buttons = [ let buttons = [
self.drawButton, self.drawButton,
self.textButton, self.textButton,
@ -660,8 +664,8 @@ final class MediaEditorScreenComponent: Component {
LottieAnimationComponent( LottieAnimationComponent(
animation: LottieAnimationComponent.AnimationItem( animation: LottieAnimationComponent.AnimationItem(
name: "media_backToCancel", name: "media_backToCancel",
mode: .still(position: .begin), mode: .still(position: .end),
range: nil range: (0.5, 1.0)
), ),
colors: ["__allcolors__": .white], colors: ["__allcolors__": .white],
size: CGSize(width: 33.0, height: 33.0) size: CGSize(width: 33.0, height: 33.0)
@ -693,10 +697,10 @@ final class MediaEditorScreenComponent: Component {
let doneButtonSize = self.doneButton.update( let doneButtonSize = self.doneButton.update(
transition: transition, transition: transition,
component: AnyComponent(Button( component: AnyComponent(Button(
content: AnyComponent(Image( content: AnyComponent(DoneButtonComponent(
image: state.image(.done), backgroundColor: UIColor(rgb: 0x007aff),
size: CGSize(width: 33.0, height: 33.0) icon: UIImage(bundleImageName: "Media Editor/Next")!,
)), title: "NEXT")),
action: { action: {
guard let controller = environment.controller() as? MediaEditorScreen else { guard let controller = environment.controller() as? MediaEditorScreen else {
return return
@ -707,7 +711,7 @@ final class MediaEditorScreenComponent: Component {
} }
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: 44.0, height: 44.0) containerSize: CGSize(width: availableSize.width, height: 44.0)
) )
let doneButtonFrame = CGRect( let doneButtonFrame = CGRect(
origin: CGPoint(x: availableSize.width - buttonSideInset - doneButtonSize.width, y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset), origin: CGPoint(x: availableSize.width - buttonSideInset - doneButtonSize.width, y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset),
@ -728,8 +732,8 @@ final class MediaEditorScreenComponent: Component {
buttonsAvailableWidth = previewSize.width + 260.0 buttonsAvailableWidth = previewSize.width + 260.0
buttonsLeftOffset = floorToScreenPixels((availableSize.width - buttonsAvailableWidth) / 2.0) buttonsLeftOffset = floorToScreenPixels((availableSize.width - buttonsAvailableWidth) / 2.0)
} else { } else {
buttonsAvailableWidth = availableSize.width buttonsAvailableWidth = floor(availableSize.width - cancelButtonSize.width * 0.66 - (doneButtonSize.width - cancelButtonSize.width * 0.33) - buttonSideInset * 2.0)
buttonsLeftOffset = 0.0 buttonsLeftOffset = floorToScreenPixels(buttonSideInset + cancelButtonSize.width * 0.66)
} }
let drawButtonSize = self.drawButton.update( let drawButtonSize = self.drawButton.update(
@ -747,7 +751,7 @@ final class MediaEditorScreenComponent: Component {
containerSize: CGSize(width: 40.0, height: 40.0) containerSize: CGSize(width: 40.0, height: 40.0)
) )
let drawButtonFrame = CGRect( let drawButtonFrame = CGRect(
origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 4.0 - 3.0 - drawButtonSize.width / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 1.0), origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 - drawButtonSize.width / 2.0 - 3.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 1.0),
size: drawButtonSize size: drawButtonSize
) )
if let drawButtonView = self.drawButton.view { if let drawButtonView = self.drawButton.view {
@ -776,7 +780,7 @@ final class MediaEditorScreenComponent: Component {
containerSize: CGSize(width: 40.0, height: 40.0) containerSize: CGSize(width: 40.0, height: 40.0)
) )
let textButtonFrame = CGRect( let textButtonFrame = CGRect(
origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 2.5 + 5.0 - textButtonSize.width / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0), origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 * 2.0 - textButtonSize.width / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0),
size: textButtonSize size: textButtonSize
) )
if let textButtonView = self.textButton.view { if let textButtonView = self.textButton.view {
@ -805,7 +809,7 @@ final class MediaEditorScreenComponent: Component {
containerSize: CGSize(width: 40.0, height: 40.0) containerSize: CGSize(width: 40.0, height: 40.0)
) )
let stickerButtonFrame = CGRect( let stickerButtonFrame = CGRect(
origin: CGPoint(x: floorToScreenPixels(availableSize.width - buttonsLeftOffset - buttonsAvailableWidth / 2.5 - 5.0 - stickerButtonSize.width / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0), origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 * 3.0 - stickerButtonSize.width / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0),
size: stickerButtonSize size: stickerButtonSize
) )
if let stickerButtonView = self.stickerButton.view { if let stickerButtonView = self.stickerButton.view {
@ -834,7 +838,7 @@ final class MediaEditorScreenComponent: Component {
containerSize: CGSize(width: 40.0, height: 40.0) containerSize: CGSize(width: 40.0, height: 40.0)
) )
let toolsButtonFrame = CGRect( let toolsButtonFrame = CGRect(
origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 4.0 * 3.0 + 3.0 - toolsButtonSize.width / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 1.0), origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 * 4.0 - toolsButtonSize.width / 2.0 + 3.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 1.0),
size: toolsButtonSize size: toolsButtonSize
) )
if let toolsButtonView = self.toolsButton.view { if let toolsButtonView = self.toolsButton.view {
@ -923,9 +927,6 @@ final class MediaEditorScreenComponent: Component {
default: default:
timeoutValue = "24" timeoutValue = "24"
} }
if component.privacy.archive {
timeoutValue = ""
}
timeoutSelected = false timeoutSelected = false
var inputPanelAvailableWidth = previewSize.width var inputPanelAvailableWidth = previewSize.width
@ -1530,7 +1531,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
struct State { struct State {
var privacy: MediaEditorResultPrivacy = MediaEditorResultPrivacy(privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 86400, archive: false) var privacy: MediaEditorResultPrivacy = MediaEditorResultPrivacy(
privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []),
timeout: 86400,
isForwardingDisabled: false,
pin: false
)
} }
var state = State() { var state = State() {
@ -1846,8 +1852,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
} }
} }
} else if case let .asset(asset) = subject, asset.mediaType == .video { }
//#if DEBUG //#if DEBUG
// if case let .asset(asset) = subject, asset.mediaType == .video {
// let videoEntity = DrawingStickerEntity(content: .dualVideoReference) // let videoEntity = DrawingStickerEntity(content: .dualVideoReference)
// videoEntity.referenceDrawingSize = storyDimensions // videoEntity.referenceDrawingSize = storyDimensions
// videoEntity.scale = 1.49 // videoEntity.scale = 1.49
@ -1863,8 +1870,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
// } // }
// } // }
// } // }
// }
//#endif //#endif
}
self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in
if let self, let colors { if let self, let colors {
@ -2571,7 +2578,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 5.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 5.0), size: CGSize())
let text: String let text: String
if controller.state.privacy.archive { if controller.state.privacy.pin {
text = "Story will be kept on your page." text = "Story will be kept on your page."
} else { } else {
text = "Story will disappear in 24 hours." text = "Story will disappear in 24 hours."
@ -3040,7 +3047,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if isEditing { if isEditing {
if let initialPrivacy { if let initialPrivacy {
self.state.privacy = MediaEditorResultPrivacy(privacy: initialPrivacy, timeout: 86400, archive: false) self.state.privacy = MediaEditorResultPrivacy(
privacy: initialPrivacy,
timeout: 86400,
isForwardingDisabled: false,
pin: false
)
} }
} else { } else {
let _ = combineLatest( let _ = combineLatest(
@ -3050,7 +3062,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
).start(next: { [weak self] state, peer in ).start(next: { [weak self] state, peer in
if let self, var privacy = state?.privacy { if let self, var privacy = state?.privacy {
if case let .user(user) = peer, !user.isPremium && privacy.timeout != 86400 { if case let .user(user) = peer, !user.isPremium && privacy.timeout != 86400 {
privacy = MediaEditorResultPrivacy(privacy: privacy.privacy, timeout: 86400, archive: false) privacy = MediaEditorResultPrivacy(privacy: privacy.privacy, timeout: 86400, isForwardingDisabled: privacy.isForwardingDisabled, pin: privacy.pin)
} }
self.state.privacy = privacy self.state.privacy = privacy
} }
@ -3080,36 +3092,41 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let privacy = privacy ?? self.state.privacy let privacy = privacy ?? self.state.privacy
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .stories, initialPeerIds: Set(privacy.privacy.additionallyIncludePeers)) let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .stories(editing: false), initialPeerIds: Set(privacy.privacy.additionallyIncludePeers))
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
return return
} }
let initialPrivacy = privacy.privacy let initialPrivacy = privacy.privacy
let timeout = privacy.timeout let timeout = privacy.timeout
let archive = privacy.archive
self.push( self.push(
ShareWithPeersScreen( ShareWithPeersScreen(
context: self.context, context: self.context,
initialPrivacy: initialPrivacy, initialPrivacy: initialPrivacy,
timeout: timeout, allowScreenshots: !privacy.isForwardingDisabled,
pin: privacy.pin,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] privacy in completion: { [weak self] privacy, allowScreenshots, pin in
guard let self else { guard let self else {
return return
} }
self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive) self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, isForwardingDisabled: !allowScreenshots, pin: pin)
completion() completion()
}, },
editCategory: { [weak self] privacy in editCategory: { [weak self] privacy, allowScreenshots, pin in
guard let self else { guard let self else {
return return
} }
self.openEditCategory(privacy: privacy, completion: { [weak self] privacy in self.openEditCategory(privacy: privacy, isForwardingDisabled: !allowScreenshots, pin: pin, completion: { [weak self] privacy in
guard let self else { guard let self else {
return return
} }
self.openPrivacySettings(MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive), completion: completion) self.openPrivacySettings(MediaEditorResultPrivacy(
privacy: privacy,
timeout: timeout,
isForwardingDisabled: !allowScreenshots,
pin: pin
), completion: completion)
}) })
} }
) )
@ -3117,7 +3134,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}) })
} }
private func openEditCategory(privacy: EngineStoryPrivacy, completion: @escaping (EngineStoryPrivacy) -> Void) { private func openEditCategory(privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, pin: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) {
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .contacts(privacy.base), initialPeerIds: Set(privacy.additionallyIncludePeers)) let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .contacts(privacy.base), initialPeerIds: Set(privacy.additionallyIncludePeers))
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
@ -3128,9 +3145,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
ShareWithPeersScreen( ShareWithPeersScreen(
context: self.context, context: self.context,
initialPrivacy: privacy, initialPrivacy: privacy,
timeout: 0, allowScreenshots: !isForwardingDisabled,
pin: pin,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] result in completion: { [weak self] result, isForwardingDisabled, pin in
guard let self else { guard let self else {
return return
} }
@ -3141,7 +3159,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
completion(result) completion(result)
} }
}, },
editCategory: { _ in } editCategory: { _, _, _ in }
) )
) )
}) })
@ -3152,16 +3170,16 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
let updateTimeout: (Int?, Bool) -> Void = { [weak self] timeout, archive in let updateTimeout: (Int?) -> Void = { [weak self] timeout in
guard let self else { guard let self else {
return return
} }
self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, archive: archive) self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, isForwardingDisabled: self.state.privacy.isForwardingDisabled, pin: self.state.privacy.pin)
} }
let title = "Choose how long the story will be visible." let title = "Choose how long the story will be visible."
let currentValue = self.state.privacy.timeout let currentValue = self.state.privacy.timeout
let currentArchived = self.state.privacy.archive let currentArchived = self.state.privacy.pin
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction))) items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
@ -3176,7 +3194,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
a(.default) a(.default)
if hasPremium { if hasPremium {
updateTimeout(3600 * 6, false) updateTimeout(3600 * 6)
} else { } else {
self?.presentTimeoutPremiumSuggestion(3600 * 6) self?.presentTimeoutPremiumSuggestion(3600 * 6)
} }
@ -3191,7 +3209,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
a(.default) a(.default)
if hasPremium { if hasPremium {
updateTimeout(3600 * 12, false) updateTimeout(3600 * 12)
} else { } else {
self?.presentTimeoutPremiumSuggestion(3600 * 12) self?.presentTimeoutPremiumSuggestion(3600 * 12)
} }
@ -3201,7 +3219,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}, action: { _, a in }, action: { _, a in
a(.default) a(.default)
updateTimeout(86400, false) updateTimeout(86400)
}))) })))
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in
if !hasPremium { if !hasPremium {
@ -3213,7 +3231,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
a(.default) a(.default)
if hasPremium { if hasPremium {
updateTimeout(86400 * 2, false) updateTimeout(86400 * 2)
} else { } else {
self?.presentTimeoutPremiumSuggestion(86400 * 2) self?.presentTimeoutPremiumSuggestion(86400 * 2)
} }
@ -3873,7 +3891,7 @@ final class PrivacyButtonComponent: CombinedComponent {
let text = text.update( let text = text.update(
component: Text( component: Text(
text: "\(context.component.text)", text: context.component.text,
font: Font.medium(14.0), font: Font.medium(14.0),
color: .white color: .white
), ),
@ -3907,6 +3925,92 @@ final class PrivacyButtonComponent: CombinedComponent {
} }
} }
final class DoneButtonComponent: CombinedComponent {
let backgroundColor: UIColor
let icon: UIImage
let title: String?
init(
backgroundColor: UIColor,
icon: UIImage,
title: String?
) {
self.backgroundColor = backgroundColor
self.icon = icon
self.title = title
}
static func ==(lhs: DoneButtonComponent, rhs: DoneButtonComponent) -> Bool {
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
static var body: Body {
let background = Child(RoundedRectangle.self)
let icon = Child(Image.self)
let text = Child(Text.self)
return { context in
let icon = icon.update(
component: Image(image: context.component.icon, tintColor: .white, size: CGSize(width: 10.0, height: 16.0)),
availableSize: CGSize(width: 180.0, height: 100.0),
transition: .immediate
)
let backgroundHeight: CGFloat = 33.0
var textWidth: CGFloat = 0.0
var title: _UpdatedChildComponent?
if let titleText = context.component.title {
title = text.update(
component: Text(
text: titleText,
font: Font.with(size: 16.0, design: .round, weight: .semibold),
color: .white
),
availableSize: CGSize(width: 180.0, height: 100.0),
transition: .immediate
)
textWidth = title!.size.width
}
var backgroundSize = CGSize(width: 33.0, height: backgroundHeight)
if !textWidth.isZero {
backgroundSize.width += textWidth + 7.0
}
let background = background.update(
component: RoundedRectangle(color: context.component.backgroundColor, cornerRadius: backgroundHeight / 2.0),
availableSize: backgroundSize,
transition: .immediate
)
context.add(background
.position(CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0))
.cornerRadius(min(backgroundSize.width, backgroundSize.height) / 2.0)
.clipsToBounds(true)
)
if let title {
context.add(title
.position(CGPoint(x: title.size.width / 2.0 + 15.0, y: backgroundHeight / 2.0))
)
}
context.add(icon
.position(CGPoint(x: background.size.width - 16.0, y: backgroundSize.height / 2.0))
)
return backgroundSize
}
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
private let sourceView: UIView private let sourceView: UIView

View File

@ -284,6 +284,8 @@ private final class MediaToolsScreenComponent: Component {
func animateOutToEditor(completion: @escaping () -> Void) { func animateOutToEditor(completion: @escaping () -> Void) {
self.animatingOut = true self.animatingOut = true
self.cancelButton.view?.isHidden = true
let buttons = [ let buttons = [
self.adjustmentsButton, self.adjustmentsButton,
self.tintButton, self.tintButton,

View File

@ -35,6 +35,8 @@ swift_library(
"//submodules/LocalizedPeerData", "//submodules/LocalizedPeerData",
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/SwitchComponent",
"//submodules/TooltipUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -20,7 +20,6 @@ final class CategoryListItemComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let sideInset: CGFloat
let title: String let title: String
let color: ShareWithPeersScreenComponent.CategoryColor let color: ShareWithPeersScreenComponent.CategoryColor
let iconName: String? let iconName: String?
@ -33,7 +32,6 @@ final class CategoryListItemComponent: Component {
init( init(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
sideInset: CGFloat,
title: String, title: String,
color: ShareWithPeersScreenComponent.CategoryColor, color: ShareWithPeersScreenComponent.CategoryColor,
iconName: String?, iconName: String?,
@ -45,7 +43,6 @@ final class CategoryListItemComponent: Component {
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.sideInset = sideInset
self.title = title self.title = title
self.color = color self.color = color
self.iconName = iconName self.iconName = iconName
@ -63,9 +60,6 @@ final class CategoryListItemComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.title != rhs.title { if lhs.title != rhs.title {
return false return false
} }
@ -179,11 +173,11 @@ final class CategoryListItemComponent: Component {
let contextInset: CGFloat = 0.0 let contextInset: CGFloat = 0.0
let height: CGFloat = 60.0 let height: CGFloat = 56.0
let verticalInset: CGFloat = 1.0 let verticalInset: CGFloat = 0.0
var leftInset: CGFloat = 62.0 + component.sideInset var leftInset: CGFloat = 62.0
let rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset let rightInset: CGFloat = contextInset * 2.0 + 8.0
var avatarLeftInset: CGFloat = component.sideInset + 10.0 var avatarLeftInset: CGFloat = 10.0
if case let .editing(isSelected, isTinted) = component.selectionState { if case let .editing(isSelected, isTinted) = component.selectionState {
leftInset += 44.0 leftInset += 44.0
@ -310,7 +304,7 @@ final class CategoryListItemComponent: Component {
labelArrowView.isUserInteractionEnabled = false labelArrowView.isUserInteractionEnabled = false
self.containerButton.addSubview(labelArrowView) self.containerButton.addSubview(labelArrowView)
} }
transition.setFrame(view: labelArrowView, frame: CGRect(origin: CGPoint(x: titleFrame.minX + labelSize.width + 5.0, y: titleFrame.maxY + titleSpacing + floorToScreenPixels(labelSize.height / 2.0 - labelArrowSize.height / 2.0)), size: labelArrowSize)) transition.setFrame(view: labelArrowView, frame: CGRect(origin: CGPoint(x: titleFrame.minX + labelSize.width + 5.0, y: titleFrame.maxY + titleSpacing + floorToScreenPixels(labelSize.height / 2.0 - labelArrowSize.height / 2.0) + 1.0 ), size: labelArrowSize))
} }
if themeUpdated { if themeUpdated {

View File

@ -0,0 +1,157 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import MultilineTextComponent
import TelegramPresentationData
import SwitchComponent
final class OptionListItemComponent: Component {
enum SelectionState: Equatable {
case none
case editing(isSelected: Bool, isTinted: Bool)
}
let theme: PresentationTheme
let title: String
let hasNext: Bool
let selected: Bool
let selectionChanged: (Bool) -> Void
init(
theme: PresentationTheme,
title: String,
hasNext: Bool,
selected: Bool,
selectionChanged: @escaping (Bool) -> Void
) {
self.theme = theme
self.title = title
self.hasNext = hasNext
self.selected = selected
self.selectionChanged = selectionChanged
}
static func ==(lhs: OptionListItemComponent, rhs: OptionListItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.selected != rhs.selected {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
return true
}
final class View: UIView {
private let containerButton: HighlightTrackingButton
private let title = ComponentView<Empty>()
private let switchComponent = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
private var component: OptionListItemComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.separatorLayer = SimpleLayer()
self.containerButton = HighlightTrackingButton()
super.init(frame: frame)
self.layer.addSublayer(self.separatorLayer)
self.addSubview(self.containerButton)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
// guard let component = self.component else {
// return
// }
// if case .editing(true, _) = component.selectionState {
// component.secondaryAction()
// } else {
// component.action()
// }
}
func update(component: OptionListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
self.component = component
self.state = state
let height: CGFloat = 44.0
let verticalInset: CGFloat = 0.0
let leftInset: CGFloat = 16.0
let rightInset: CGFloat = 16.0
let switchSize = self.switchComponent.update(
transition: .immediate,
component: AnyComponent(SwitchComponent(
tintColor: nil,
value: component.selected,
valueUpdated: { selected in
component.selectionChanged(selected)
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - titleSize.height) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
}
if let switchView = self.switchComponent.view {
if switchView.superview == nil {
self.containerButton.addSubview(switchView)
}
transition.setFrame(view: switchView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - switchSize.width, y: floorToScreenPixels((height - switchSize.height) / 2.0)), size: switchSize))
}
if themeUpdated {
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
self.separatorLayer.isHidden = !component.hasNext
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalInset), size: CGSize(width: availableSize.width, height: height - verticalInset * 2.0))
transition.setFrame(view: self.containerButton, frame: containerFrame)
return CGSize(width: availableSize.width, height: height)
}
}
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)
}
}

View File

@ -1,344 +0,0 @@
//import Foundation
//import UIKit
//import Display
//import AsyncDisplayKit
//import ComponentFlow
//import SwiftSignalKit
//import AccountContext
//import TelegramCore
//import MultilineTextComponent
//import AvatarNode
//import TelegramPresentationData
//import CheckNode
//import TelegramStringFormatting
//import PeerPresenceStatusManager
//
//private let avatarFont = avatarPlaceholderFont(size: 15.0)
//
//final class PeerListItemComponent: Component {
// enum SelectionState: Equatable {
// case none
// case editing(isSelected: Bool, isTinted: Bool)
// }
//
// let context: AccountContext
// let theme: PresentationTheme
// let strings: PresentationStrings
// let sideInset: CGFloat
// let title: String
// let peer: EnginePeer?
// let subtitle: String?
// let presence: EnginePeer.Presence?
// let selectionState: SelectionState
// let hasNext: Bool
// let action: (EnginePeer) -> Void
//
// init(
// context: AccountContext,
// theme: PresentationTheme,
// strings: PresentationStrings,
// sideInset: CGFloat,
// title: String,
// peer: EnginePeer?,
// subtitle: String?,
// presence: EnginePeer.Presence?,
// selectionState: SelectionState,
// hasNext: Bool,
// action: @escaping (EnginePeer) -> Void
// ) {
// self.context = context
// self.theme = theme
// self.strings = strings
// self.sideInset = sideInset
// self.title = title
// self.peer = peer
// self.subtitle = subtitle
// self.presence = presence
// self.selectionState = selectionState
// self.hasNext = hasNext
// self.action = action
// }
//
// static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool {
// if lhs.context !== rhs.context {
// return false
// }
// if lhs.theme !== rhs.theme {
// return false
// }
// if lhs.strings !== rhs.strings {
// return false
// }
// if lhs.sideInset != rhs.sideInset {
// return false
// }
// if lhs.title != rhs.title {
// return false
// }
// if lhs.peer != rhs.peer {
// return false
// }
// if lhs.subtitle != rhs.subtitle {
// return false
// }
// if lhs.presence != rhs.presence {
// return false
// }
// if lhs.selectionState != rhs.selectionState {
// return false
// }
// if lhs.hasNext != rhs.hasNext {
// return false
// }
// return true
// }
//
// final class View: UIView {
// private let containerButton: HighlightTrackingButton
//
// private let title = ComponentView<Empty>()
// private let label = ComponentView<Empty>()
// private let separatorLayer: SimpleLayer
// private let avatarNode: AvatarNode
//
// private var checkLayer: CheckLayer?
//
// private var component: PeerListItemComponent?
// private weak var state: EmptyComponentState?
//
// private var presenceManager: PeerPresenceStatusManager?
//
// override init(frame: CGRect) {
// self.separatorLayer = SimpleLayer()
//
// self.containerButton = HighlightTrackingButton()
//
// self.avatarNode = AvatarNode(font: avatarFont)
// self.avatarNode.isLayerBacked = true
//
// super.init(frame: frame)
//
// self.layer.addSublayer(self.separatorLayer)
// self.addSubview(self.containerButton)
// self.containerButton.layer.addSublayer(self.avatarNode.layer)
//
// self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
// }
//
// required init?(coder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
// }
//
// @objc private func pressed() {
// guard let component = self.component, let peer = component.peer else {
// return
// }
// component.action(peer)
// }
//
// func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
// let animationHint = transition.userData(ShareWithPeersScreenComponent.AnimationHint.self)
// var synchronousLoad = false
// if let animationHint, animationHint.contentReloaded {
// synchronousLoad = true
// }
//
// let themeUpdated = self.component?.theme !== component.theme
//
// var hasSelectionUpdated = false
// if let previousComponent = self.component {
// switch previousComponent.selectionState {
// case .none:
// if case .none = component.selectionState {
// } else {
// hasSelectionUpdated = true
// }
// case .editing:
// if case .editing = component.selectionState {
// } else {
// hasSelectionUpdated = true
// }
// }
// }
//
// if let presence = component.presence {
// let presenceManager: PeerPresenceStatusManager
// if let current = self.presenceManager {
// presenceManager = current
// } else {
// presenceManager = PeerPresenceStatusManager(update: { [weak self] in
// self?.state?.updated(transition: .immediate)
// })
// self.presenceManager = presenceManager
// }
// presenceManager.reset(presence: presence)
// } else {
// if self.presenceManager != nil {
// self.presenceManager = nil
// }
// }
//
// self.component = component
// self.state = state
//
// let contextInset: CGFloat = 0.0
//
// let height: CGFloat = 60.0
// let verticalInset: CGFloat = 1.0
// var leftInset: CGFloat = 62.0 + component.sideInset
// let rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset
// var avatarLeftInset: CGFloat = component.sideInset + 10.0
//
// if case let .editing(isSelected, isTinted) = component.selectionState {
// leftInset += 44.0
// avatarLeftInset += 44.0
// let checkSize: CGFloat = 22.0
//
// let checkLayer: CheckLayer
// if let current = self.checkLayer {
// checkLayer = current
// if themeUpdated {
// var theme = CheckNodeTheme(theme: component.theme, style: .plain)
// if isTinted {
// theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5)
// }
// checkLayer.theme = theme
// }
// checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate)
// } else {
// var theme = CheckNodeTheme(theme: component.theme, style: .plain)
// if isTinted {
// theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5)
// }
// checkLayer = CheckLayer(theme: theme)
// self.checkLayer = checkLayer
// self.containerButton.layer.addSublayer(checkLayer)
// checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))
// checkLayer.setSelected(isSelected, animated: false)
// checkLayer.setNeedsDisplay()
// }
// transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: floor((54.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
// } else {
// if let checkLayer = self.checkLayer {
// self.checkLayer = nil
// transition.setPosition(layer: checkLayer, position: CGPoint(x: -checkLayer.bounds.width * 0.5, y: checkLayer.position.y), completion: { [weak checkLayer] _ in
// checkLayer?.removeFromSuperlayer()
// })
// }
// }
//
// let avatarSize: CGFloat = 40.0
//
// let avatarFrame = CGRect(origin: CGPoint(x: avatarLeftInset, y: floor((height - verticalInset * 2.0 - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
// if self.avatarNode.bounds.isEmpty {
// self.avatarNode.frame = avatarFrame
// } else {
// transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame)
// }
// if let peer = component.peer {
// let clipStyle: AvatarNodeClipStyle
// if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
// clipStyle = .roundedRect
// } else {
// clipStyle = .round
// }
// self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
// }
//
// let labelData: (String, Bool)
//
// if let presence = component.presence {
// let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
// labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(timestamp))
// } else if let subtitle = component.subtitle {
// labelData = (subtitle, false)
// } else if case .legacyGroup = component.peer {
// labelData = (component.strings.Group_Status, false)
// } else if case let .channel(channel) = component.peer {
// if case .group = channel.info {
// labelData = (component.strings.Group_Status, false)
// } else {
// labelData = (component.strings.Channel_Status, false)
// }
// } else {
// labelData = (component.strings.Group_Status, false)
// }
//
// let labelSize = self.label.update(
// transition: .immediate,
// component: AnyComponent(MultilineTextComponent(
// text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor))
// )),
// environment: {},
// containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
// )
//
// let previousTitleFrame = self.title.view?.frame
// var previousTitleContents: UIView?
// if hasSelectionUpdated && !"".isEmpty {
// previousTitleContents = self.title.view?.snapshotView(afterScreenUpdates: false)
// }
//
// let titleSize = self.title.update(
// transition: .immediate,
// component: AnyComponent(MultilineTextComponent(
// text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
// )),
// environment: {},
// containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
// )
//
// let titleSpacing: CGFloat = 1.0
// let centralContentHeight: CGFloat = titleSize.height + labelSize.height + titleSpacing
//
// let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize)
// if let titleView = self.title.view {
// if titleView.superview == nil {
// titleView.isUserInteractionEnabled = false
// self.containerButton.addSubview(titleView)
// }
// titleView.frame = titleFrame
// if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x {
// transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true)
// }
//
// if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize {
// previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size)
// self.addSubview(previousTitleContents)
//
// transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size))
// transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in
// previousTitleContents?.removeFromSuperview()
// })
// transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
// }
// }
// if let labelView = self.label.view {
// if labelView.superview == nil {
// labelView.isUserInteractionEnabled = false
// self.containerButton.addSubview(labelView)
// }
// transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing), size: labelSize))
// }
//
// if themeUpdated {
// self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
// }
// transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
// self.separatorLayer.isHidden = !component.hasNext
//
// let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0))
// transition.setFrame(view: self.containerButton, frame: containerFrame)
//
// return CGSize(width: availableSize.width, height: height)
// }
// }
//
// 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)
// }
//}

View File

@ -7,16 +7,16 @@ import MultilineTextComponent
final class SectionHeaderComponent: Component { final class SectionHeaderComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let sideInset: CGFloat let style: ShareWithPeersScreenComponent.Style
let title: String let title: String
init( init(
theme: PresentationTheme, theme: PresentationTheme,
sideInset: CGFloat, style: ShareWithPeersScreenComponent.Style,
title: String title: String
) { ) {
self.theme = theme self.theme = theme
self.sideInset = sideInset self.style = style
self.title = title self.title = title
} }
@ -24,7 +24,7 @@ final class SectionHeaderComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.sideInset != rhs.sideInset { if lhs.style != rhs.style {
return false return false
} }
if lhs.title != rhs.title { if lhs.title != rhs.title {
@ -59,13 +59,19 @@ final class SectionHeaderComponent: Component {
self.state = state self.state = state
let height: CGFloat = 28.0 let height: CGFloat = 28.0
let leftInset: CGFloat = component.sideInset let leftInset: CGFloat = 16.0
let rightInset: CGFloat = component.sideInset let rightInset: CGFloat = 0.0
let previousTitleFrame = self.title.view?.frame let previousTitleFrame = self.title.view?.frame
if themeUpdated { if themeUpdated {
switch component.style {
case .plain:
self.backgroundView.isHidden = false
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
case .blocks:
self.backgroundView.isHidden = true
}
} }
let titleSize = self.title.update( let titleSize = self.title.update(

View File

@ -20,6 +20,7 @@ import AvatarNode
import LocalizedPeerData import LocalizedPeerData
import PeerListItemComponent import PeerListItemComponent
import LottieComponent import LottieComponent
import TooltipUI
final class ShareWithPeersScreenComponent: Component { final class ShareWithPeersScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -27,30 +28,33 @@ final class ShareWithPeersScreenComponent: Component {
let context: AccountContext let context: AccountContext
let stateContext: ShareWithPeersScreen.StateContext let stateContext: ShareWithPeersScreen.StateContext
let initialPrivacy: EngineStoryPrivacy let initialPrivacy: EngineStoryPrivacy
let timeout: Int let screenshot: Bool
let pin: Bool
let categoryItems: [CategoryItem] let categoryItems: [CategoryItem]
let completion: (EngineStoryPrivacy) -> Void let optionItems: [OptionItem]
let editCategory: (EngineStoryPrivacy) -> Void let completion: (EngineStoryPrivacy, Bool, Bool) -> Void
let secondaryAction: () -> Void let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
init( init(
context: AccountContext, context: AccountContext,
stateContext: ShareWithPeersScreen.StateContext, stateContext: ShareWithPeersScreen.StateContext,
initialPrivacy: EngineStoryPrivacy, initialPrivacy: EngineStoryPrivacy,
timeout: Int, screenshot: Bool,
pin: Bool,
categoryItems: [CategoryItem], categoryItems: [CategoryItem],
completion: @escaping (EngineStoryPrivacy) -> Void, optionItems: [OptionItem],
editCategory: @escaping (EngineStoryPrivacy) -> Void, completion: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
secondaryAction: @escaping () -> Void editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
) { ) {
self.context = context self.context = context
self.stateContext = stateContext self.stateContext = stateContext
self.initialPrivacy = initialPrivacy self.initialPrivacy = initialPrivacy
self.timeout = timeout self.screenshot = screenshot
self.pin = pin
self.categoryItems = categoryItems self.categoryItems = categoryItems
self.optionItems = optionItems
self.completion = completion self.completion = completion
self.editCategory = editCategory self.editCategory = editCategory
self.secondaryAction = secondaryAction
} }
static func ==(lhs: ShareWithPeersScreenComponent, rhs: ShareWithPeersScreenComponent) -> Bool { static func ==(lhs: ShareWithPeersScreenComponent, rhs: ShareWithPeersScreenComponent) -> Bool {
@ -63,16 +67,26 @@ final class ShareWithPeersScreenComponent: Component {
if lhs.initialPrivacy != rhs.initialPrivacy { if lhs.initialPrivacy != rhs.initialPrivacy {
return false return false
} }
if lhs.timeout != rhs.timeout { if lhs.screenshot != rhs.screenshot {
return false
}
if lhs.pin != rhs.pin {
return false return false
} }
if lhs.categoryItems != rhs.categoryItems { if lhs.categoryItems != rhs.categoryItems {
return false return false
} }
if lhs.optionItems != rhs.optionItems {
return false
}
return true return true
} }
enum Style {
case plain
case blocks
}
private struct ItemLayout: Equatable { private struct ItemLayout: Equatable {
struct Section: Equatable { struct Section: Equatable {
var id: Int var id: Int
@ -97,6 +111,7 @@ final class ShareWithPeersScreenComponent: Component {
} }
} }
var style: ShareWithPeersScreenComponent.Style
var containerSize: CGSize var containerSize: CGSize
var containerInset: CGFloat var containerInset: CGFloat
var bottomInset: CGFloat var bottomInset: CGFloat
@ -107,7 +122,8 @@ final class ShareWithPeersScreenComponent: Component {
var contentHeight: CGFloat var contentHeight: CGFloat
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat, sideInset: CGFloat, navigationHeight: CGFloat, sections: [Section]) { init(style: ShareWithPeersScreenComponent.Style, containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat, sideInset: CGFloat, navigationHeight: CGFloat, sections: [Section]) {
self.style = style
self.containerSize = containerSize self.containerSize = containerSize
self.containerInset = containerInset self.containerInset = containerInset
self.bottomInset = bottomInset self.bottomInset = bottomInset
@ -155,6 +171,13 @@ final class ShareWithPeersScreenComponent: Component {
case violet case violet
} }
enum CategoryId: Int, Hashable {
case everyone = 0
case contacts = 1
case closeFriends = 2
case selectedContacts = 3
}
final class CategoryItem: Equatable { final class CategoryItem: Equatable {
let id: CategoryId let id: CategoryId
let title: String let title: String
@ -204,11 +227,29 @@ final class ShareWithPeersScreenComponent: Component {
} }
} }
enum CategoryId: Int, Hashable { enum OptionId: Int, Hashable {
case everyone = 0 case screenshot = 0
case contacts = 1 case pin = 1
case closeFriends = 2 }
case selectedContacts = 3
final class OptionItem: Equatable {
let id: OptionId
let title: String
init(
id: OptionId,
title: String
) {
self.id = id
self.title = title
}
static func ==(lhs: OptionItem, rhs: OptionItem) -> Bool {
if lhs === rhs {
return true
}
return false
}
} }
final class View: UIView, UIScrollViewDelegate { final class View: UIView, UIScrollViewDelegate {
@ -239,17 +280,20 @@ final class ShareWithPeersScreenComponent: Component {
private let categoryTemplateItem = ComponentView<Empty>() private let categoryTemplateItem = ComponentView<Empty>()
private let peerTemplateItem = ComponentView<Empty>() private let peerTemplateItem = ComponentView<Empty>()
private let optionTemplateItem = ComponentView<Empty>()
private let itemContainerView: UIView private let itemContainerView: UIView
private var visibleSectionHeaders: [Int: ComponentView<Empty>] = [:] private var visibleSectionHeaders: [Int: ComponentView<Empty>] = [:]
private var visibleItems: [AnyHashable: ComponentView<Empty>] = [:] private var visibleItems: [AnyHashable: ComponentView<Empty>] = [:]
private var visibleSectionBackgrounds: [Int: UIView] = [:]
private var visibleSectionFooters: [Int: ComponentView<Empty>] = [:]
private var ignoreScrolling: Bool = false private var ignoreScrolling: Bool = false
private var isDismissed: Bool = false private var isDismissed: Bool = false
private var selectedPeers: [EnginePeer.Id] = [] private var selectedPeers: [EnginePeer.Id] = []
private var selectedCategories = Set<CategoryId>() private var selectedCategories = Set<CategoryId>()
private var selectedPeersByCategory: [CategoryId: [EnginePeer.Id]] = [:] private var selectedOptions = Set<OptionId>()
private var component: ShareWithPeersScreenComponent? private var component: ShareWithPeersScreenComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -389,8 +433,67 @@ final class ShareWithPeersScreenComponent: Component {
} }
} }
private func presentOptionsTooltip(optionId: OptionId) {
guard let component = self.component, let controller = self.environment?.controller() else {
return
}
let animationName: String
let text: String
switch optionId {
case .screenshot:
if self.selectedOptions.contains(.screenshot) {
if self.selectedCategories.contains(.everyone) {
animationName = "anim_savemedia"
text = "Downloading, sharing and taking screenshots will be enabled for this story."
} else {
animationName = "anim_savemedia"
text = "Downloading and taking screenshots will be enabled for this story."
}
} else {
if self.selectedCategories.contains(.everyone) {
animationName = "premium_unlock"
text = "Downloading, sharing and taking screenshots will be disabled for this story."
} else {
animationName = "premium_unlock"
text = "Downloading and taking screenshots will be disabled for this story."
}
}
case .pin:
if self.selectedOptions.contains(.pin) {
animationName = "anim_profileadd"
text = "Users allowed to view your story will see it on your page event after it expires."
} else {
animationName = "anim_autoremove_on"
text = "The story will disappear after it expires."
}
}
let tooltipScreen = TooltipScreen(
context: component.context,
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .markdown(text: text),
style: .wide,
icon: .animation(name: animationName, delay: 0.0, tintColor: .white),
location: .top,
displayDuration: .custom(4.0),
shouldDismissOnTouch: { point in
return .ignore
}
)
controller.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: true)
}
})
controller.present(tooltipScreen, in: .window(.root))
}
private func updateScrolling(transition: Transition) { private func updateScrolling(transition: Transition) {
guard let component = self.component, let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else { guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
return return
} }
guard let stateValue = self.effectiveStateValue else { guard let stateValue = self.effectiveStateValue else {
@ -412,9 +515,7 @@ final class ShareWithPeersScreenComponent: Component {
var topOffsetFraction = topOffset / topOffsetDistance var topOffsetFraction = topOffset / topOffsetDistance
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
let transitionFactor: CGFloat = 1.0 - topOffsetFraction //let transitionFactor: CGFloat = 1.0 - topOffsetFraction
let _ = transitionFactor
let _ = controller
//controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) //controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
var visibleBounds = self.scrollView.bounds var visibleBounds = self.scrollView.bounds
@ -427,14 +528,42 @@ final class ShareWithPeersScreenComponent: Component {
var validIds: [AnyHashable] = [] var validIds: [AnyHashable] = []
var validSectionHeaders: [AnyHashable] = [] var validSectionHeaders: [AnyHashable] = []
var validSectionBackgrounds: [AnyHashable] = []
var sectionOffset: CGFloat = itemLayout.navigationHeight var sectionOffset: CGFloat = itemLayout.navigationHeight
for sectionIndex in 0 ..< itemLayout.sections.count { for sectionIndex in 0 ..< itemLayout.sections.count {
let section = itemLayout.sections[sectionIndex] let section = itemLayout.sections[sectionIndex]
var minSectionHeader: UIView? if case .blocks = itemLayout.style {
let sectionBackgroundFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: sectionOffset + section.insets.top), size: CGSize(width: itemLayout.containerSize.width, height: section.totalHeight - section.insets.top))
if visibleFrame.intersects(sectionBackgroundFrame) {
validSectionBackgrounds.append(section.id)
var sectionBackground: UIView
var sectionBackgroundTransition = transition
if let current = self.visibleSectionBackgrounds[section.id] {
sectionBackground = current
} else {
if !transition.animation.isImmediate {
sectionBackgroundTransition = .immediate
}
sectionBackground = UIView()
sectionBackground.backgroundColor = environment.theme.list.itemModalBlocksBackgroundColor
sectionBackground.layer.cornerRadius = 10.0
self.visibleSectionBackgrounds[section.id] = sectionBackground
}
if sectionBackground.superview == nil {
sectionBackground.isUserInteractionEnabled = false
self.itemContainerView.addSubview(sectionBackground)
}
sectionBackgroundTransition.setFrame(view: sectionBackground, frame: sectionBackgroundFrame)
}
}
var minSectionHeader: UIView?
do { do {
var sectionHeaderFrame = CGRect(origin: CGPoint(x: visibleFrame.minX, y: itemLayout.containerInset + sectionOffset - self.scrollView.bounds.minY + itemLayout.topInset), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top)) var sectionHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: itemLayout.containerInset + sectionOffset - self.scrollView.bounds.minY + itemLayout.topInset), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top))
let sectionHeaderMinY = topOffset + itemLayout.containerInset + itemLayout.navigationHeight let sectionHeaderMinY = topOffset + itemLayout.containerInset + itemLayout.navigationHeight
let sectionHeaderMaxY = itemLayout.containerInset + sectionOffset - self.scrollView.bounds.minY + itemLayout.topInset + section.totalHeight - 28.0 let sectionHeaderMaxY = itemLayout.containerInset + sectionOffset - self.scrollView.bounds.minY + itemLayout.topInset + section.totalHeight - 28.0
@ -459,19 +588,17 @@ final class ShareWithPeersScreenComponent: Component {
let sectionTitle: String let sectionTitle: String
if section.id == 0 { if section.id == 0 {
sectionTitle = "WHO CAN VIEW" sectionTitle = "WHO CAN VIEW"
} else { } else if section.id == 1 {
if case .chats = component.stateContext.subject {
sectionTitle = "CHATS"
} else {
sectionTitle = "CONTACTS" sectionTitle = "CONTACTS"
} } else {
sectionTitle = ""
} }
let _ = sectionHeader.update( let _ = sectionHeader.update(
transition: sectionHeaderTransition, transition: sectionHeaderTransition,
component: AnyComponent(SectionHeaderComponent( component: AnyComponent(SectionHeaderComponent(
theme: environment.theme, theme: environment.theme,
sideInset: 16.0, style: itemLayout.style,
title: sectionTitle title: sectionTitle
)), )),
environment: {}, environment: {},
@ -492,7 +619,7 @@ final class ShareWithPeersScreenComponent: Component {
if section.id == 0 { if section.id == 0 {
for i in 0 ..< component.categoryItems.count { for i in 0 ..< component.categoryItems.count {
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: sectionOffset + section.insets.top + CGFloat(i) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight)) let itemFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: sectionOffset + section.insets.top + CGFloat(i) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight))
if !visibleBounds.intersects(itemFrame) { if !visibleBounds.intersects(itemFrame) {
continue continue
} }
@ -519,7 +646,6 @@ final class ShareWithPeersScreenComponent: Component {
component: AnyComponent(CategoryListItemComponent( component: AnyComponent(CategoryListItemComponent(
context: component.context, context: component.context,
theme: environment.theme, theme: environment.theme,
sideInset: itemLayout.sideInset,
title: item.title, title: item.title,
color: item.iconColor, color: item.iconColor,
iconName: item.icon, iconName: item.icon,
@ -527,7 +653,7 @@ final class ShareWithPeersScreenComponent: Component {
selectionState: .editing(isSelected: self.selectedCategories.contains(item.id), isTinted: false), selectionState: .editing(isSelected: self.selectedCategories.contains(item.id), isTinted: false),
hasNext: i != component.categoryItems.count - 1, hasNext: i != component.categoryItems.count - 1,
action: { [weak self] in action: { [weak self] in
guard let self else { guard let self, let environment = self.environment, let controller = environment.controller() else {
return return
} }
if self.selectedCategories.contains(categoryId) { if self.selectedCategories.contains(categoryId) {
@ -538,7 +664,11 @@ final class ShareWithPeersScreenComponent: Component {
self.selectedCategories.insert(categoryId) self.selectedCategories.insert(categoryId)
if self.selectedPeers.isEmpty && categoryId == .selectedContacts { if self.selectedPeers.isEmpty && categoryId == .selectedContacts {
component.editCategory(EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: [])) component.editCategory(
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
)
controller.dismiss() controller.dismiss()
} }
} }
@ -560,7 +690,11 @@ final class ShareWithPeersScreenComponent: Component {
base = .nobody base = .nobody
} }
if let base { if let base {
component.editCategory(EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers)) component.editCategory(
EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
)
controller.dismiss() controller.dismiss()
} }
} }
@ -581,7 +715,7 @@ final class ShareWithPeersScreenComponent: Component {
} }
} else if section.id == 1 { } else if section.id == 1 {
for i in 0 ..< stateValue.peers.count { for i in 0 ..< stateValue.peers.count {
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: sectionOffset + section.insets.top + CGFloat(i) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight)) let itemFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: sectionOffset + section.insets.top + CGFloat(i) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight))
if !visibleBounds.intersects(itemFrame) { if !visibleBounds.intersects(itemFrame) {
continue continue
} }
@ -647,6 +781,95 @@ final class ShareWithPeersScreenComponent: Component {
itemTransition.setFrame(view: itemView, frame: itemFrame) itemTransition.setFrame(view: itemView, frame: itemFrame)
} }
} }
} else if section.id == 2 {
for i in 0 ..< component.optionItems.count {
let itemFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: sectionOffset + section.insets.top + CGFloat(i) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight))
if !visibleBounds.intersects(itemFrame) {
continue
}
let item = component.optionItems[i]
let optionId = item.id
let itemId = AnyHashable(item.id)
validIds.append(itemId)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.visibleItems[itemId] {
visibleItem = current
} else {
visibleItem = ComponentView()
if !transition.animation.isImmediate {
itemTransition = .immediate
}
self.visibleItems[itemId] = visibleItem
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(OptionListItemComponent(
theme: environment.theme,
title: item.title,
hasNext: i != component.optionItems.count - 1,
selected: self.selectedOptions.contains(item.id),
selectionChanged: { [weak self] selected in
if let self {
if selected {
self.selectedOptions.insert(optionId)
} else {
self.selectedOptions.remove(optionId)
}
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
self.state?.updated(transition: transition)
self.presentOptionsTooltip(optionId: optionId)
}
}
)),
environment: {},
containerSize: itemFrame.size
)
if let itemView = visibleItem.view {
if itemView.superview == nil {
if let minSectionHeader {
self.itemContainerView.insertSubview(itemView, belowSubview: minSectionHeader)
} else {
self.itemContainerView.addSubview(itemView)
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
}
let sectionFooter: ComponentView<Empty>
var sectionFooterTransition = transition
if let current = self.visibleSectionFooters[section.id] {
sectionFooter = current
} else {
if !transition.animation.isImmediate {
sectionFooterTransition = .immediate
}
sectionFooter = ComponentView()
self.visibleSectionFooters[section.id] = sectionFooter
}
let footerSize = sectionFooter.update(
transition: sectionFooterTransition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Keep this story on your page even after it expires in \(24) hours. Privacy settings will apply.", font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)),
maximumNumberOfLines: 0,
lineSpacing: 0.2
)),
environment: {},
containerSize: CGSize(width: itemLayout.containerSize.width - 16.0 * 2.0, height: itemLayout.contentHeight)
)
let footerFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset + 16.0, y: sectionOffset + section.totalHeight + 7.0), size: footerSize)
if let footerView = sectionFooter.view {
if footerView.superview == nil {
self.itemContainerView.addSubview(footerView)
}
sectionFooterTransition.setFrame(view: footerView, frame: footerFrame)
}
} }
sectionOffset += section.totalHeight sectionOffset += section.totalHeight
@ -678,6 +901,17 @@ final class ShareWithPeersScreenComponent: Component {
self.visibleSectionHeaders.removeValue(forKey: id) self.visibleSectionHeaders.removeValue(forKey: id)
} }
var removeSectionBackgroundIds: [Int] = []
for (id, item) in self.visibleSectionBackgrounds {
if !validSectionBackgrounds.contains(id) {
removeSectionBackgroundIds.append(id)
item.removeFromSuperview()
}
}
for id in removeSectionBackgroundIds {
self.visibleSectionBackgrounds.removeValue(forKey: id)
}
let fadeTransition = Transition.easeInOut(duration: 0.25) let fadeTransition = Transition.easeInOut(duration: 0.25)
if let searchStateContext = self.searchStateContext, case let .search(query) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty { if let searchStateContext = self.searchStateContext, case let .search(query) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
let sideInset: CGFloat = 44.0 let sideInset: CGFloat = 44.0
@ -830,7 +1064,14 @@ final class ShareWithPeersScreenComponent: Component {
let resetScrolling = self.scrollView.bounds.width != availableSize.width let resetScrolling = self.scrollView.bounds.width != availableSize.width
let sideInset: CGFloat = 0.0 var sideInset: CGFloat = 0.0
if case .stories = component.stateContext.subject {
sideInset = 16.0
self.scrollView.bounces = false
} else {
self.scrollView.bounces = true
}
let containerWidth: CGFloat let containerWidth: CGFloat
if case .regular = environment.metrics.widthClass { if case .regular = environment.metrics.widthClass {
containerWidth = 390.0 containerWidth = 390.0
@ -851,6 +1092,13 @@ final class ShareWithPeersScreenComponent: Component {
self.selectedCategories.insert(.selectedContacts) self.selectedCategories.insert(.selectedContacts)
} }
if component.screenshot {
self.selectedOptions.insert(.screenshot)
}
if component.pin {
self.selectedOptions.insert(.pin)
}
var applyState = false var applyState = false
self.defaultStateValue = component.stateContext.stateValue self.defaultStateValue = component.stateContext.stateValue
self.selectedPeers = Array(component.stateContext.initialPeerIds) self.selectedPeers = Array(component.stateContext.initialPeerIds)
@ -880,23 +1128,41 @@ final class ShareWithPeersScreenComponent: Component {
self.backgroundView.image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in self.backgroundView.image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
if case .stories = component.stateContext.subject {
context.setFillColor(environment.theme.list.modalBlocksBackgroundColor.cgColor)
} else {
context.setFillColor(environment.theme.list.plainBackgroundColor.cgColor) context.setFillColor(environment.theme.list.plainBackgroundColor.cgColor)
}
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height * 0.5), size: CGSize(width: size.width, height: size.height * 0.5))) context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height * 0.5), size: CGSize(width: size.width, height: size.height * 0.5)))
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 19) })?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 19)
if case .stories = component.stateContext.subject {
self.navigationBackgroundView.updateColor(color: environment.theme.list.modalBlocksBackgroundColor, transition: .immediate)
self.navigationSeparatorLayer.backgroundColor = UIColor.clear.cgColor
self.bottomBackgroundView.updateColor(color: environment.theme.list.modalBlocksBackgroundColor, transition: .immediate)
self.bottomSeparatorLayer.backgroundColor = UIColor.clear.cgColor
} else {
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
self.textFieldSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
self.bottomBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) self.bottomBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.bottomSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor self.bottomSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
} }
self.textFieldSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
}
let itemLayoutStyle: ShareWithPeersScreenComponent.Style
let itemsContainerWidth: CGFloat
let navigationTextFieldSize: CGSize let navigationTextFieldSize: CGSize
if case .stories = component.stateContext.subject { if case .stories = component.stateContext.subject {
itemLayoutStyle = .blocks
itemsContainerWidth = containerWidth - sideInset * 2.0
navigationTextFieldSize = .zero navigationTextFieldSize = .zero
} else { } else {
itemLayoutStyle = .plain
itemsContainerWidth = containerWidth
var tokens: [TokenListTextField.Token] = [] var tokens: [TokenListTextField.Token] = []
for peerId in self.selectedPeers { for peerId in self.selectedPeers {
guard let stateValue = self.defaultStateValue, let peer = stateValue.peers.first(where: { $0.id == peerId }) else { guard let stateValue = self.defaultStateValue, let peer = stateValue.peers.first(where: { $0.id == peerId }) else {
@ -974,12 +1240,12 @@ final class ShareWithPeersScreenComponent: Component {
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
let categoryItemSize = self.categoryTemplateItem.update( let categoryItemSize = self.categoryTemplateItem.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(CategoryListItemComponent( component: AnyComponent(CategoryListItemComponent(
context: component.context, context: component.context,
theme: environment.theme, theme: environment.theme,
sideInset: sideInset,
title: "Title", title: "Title",
color: .blue, color: .blue,
iconName: nil, iconName: nil,
@ -990,7 +1256,7 @@ final class ShareWithPeersScreenComponent: Component {
secondaryAction: {} secondaryAction: {}
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: containerWidth, height: 1000.0) containerSize: CGSize(width: itemsContainerWidth, height: 1000.0)
) )
let peerItemSize = self.peerTemplateItem.update( let peerItemSize = self.peerTemplateItem.update(
transition: transition, transition: transition,
@ -1011,7 +1277,19 @@ final class ShareWithPeersScreenComponent: Component {
} }
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: containerWidth, height: 1000.0) containerSize: CGSize(width: itemsContainerWidth, height: 1000.0)
)
let optionItemSize = self.optionTemplateItem.update(
transition: transition,
component: AnyComponent(OptionListItemComponent(
theme: environment.theme,
title: "Title",
hasNext: true,
selected: false,
selectionChanged: { _ in }
)),
environment: {},
containerSize: CGSize(width: itemsContainerWidth, height: 1000.0)
) )
var sections: [ItemLayout.Section] = [] var sections: [ItemLayout.Section] = []
@ -1023,6 +1301,12 @@ final class ShareWithPeersScreenComponent: Component {
itemHeight: categoryItemSize.height, itemHeight: categoryItemSize.height,
itemCount: component.categoryItems.count itemCount: component.categoryItems.count
)) ))
sections.append(ItemLayout.Section(
id: 2,
insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: optionItemSize.height,
itemCount: component.optionItems.count
))
} else { } else {
sections.append(ItemLayout.Section( sections.append(ItemLayout.Section(
id: 1, id: 1,
@ -1065,9 +1349,13 @@ final class ShareWithPeersScreenComponent: Component {
var actionButtonTitle = "Save Settings" var actionButtonTitle = "Save Settings"
let title: String let title: String
switch component.stateContext.subject { switch component.stateContext.subject {
case .stories: case let .stories(editing):
if editing {
title = "Edit Story"
} else {
title = "Share Story" title = "Share Story"
actionButtonTitle = "Post Story" actionButtonTitle = "Post Story"
}
case .chats: case .chats:
title = "Send as a Message" title = "Send as a Message"
case let .contacts(category): case let .contacts(category):
@ -1110,15 +1398,25 @@ final class ShareWithPeersScreenComponent: Component {
} }
navigationHeight += navigationTextFieldFrame.height navigationHeight += navigationTextFieldFrame.height
if case .stories = component.stateContext.subject {
navigationHeight += 16.0
}
let topInset: CGFloat let topInset: CGFloat
if environment.inputHeight != 0.0 || !self.navigationTextFieldState.text.isEmpty { if environment.inputHeight != 0.0 || !self.navigationTextFieldState.text.isEmpty {
topInset = 0.0 topInset = 0.0
} else { } else {
if case .stories = component.stateContext.subject { let inset: CGFloat
topInset = max(0.0, availableSize.height - containerInset - 427.0) if case let .stories(editing) = component.stateContext.subject {
if editing {
inset = 430.0
} else { } else {
topInset = max(0.0, availableSize.height - containerInset - 600.0) inset = 605.0
} }
} else {
inset = 600.0
}
topInset = max(0.0, availableSize.height - containerInset - inset)
} }
self.navigationBackgroundView.update(size: CGSize(width: containerWidth, height: navigationHeight), cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) self.navigationBackgroundView.update(size: CGSize(width: containerWidth, height: navigationHeight), cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition)
@ -1164,10 +1462,14 @@ final class ShareWithPeersScreenComponent: Component {
base = .nobody base = .nobody
} }
component.completion(EngineStoryPrivacy( component.completion(
EngineStoryPrivacy(
base: base, base: base,
additionallyIncludePeers: self.selectedPeers additionallyIncludePeers: self.selectedPeers
)) ),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
)
controller.dismiss() controller.dismiss()
} }
@ -1194,12 +1496,12 @@ final class ShareWithPeersScreenComponent: Component {
self.bottomBackgroundView.update(size: self.bottomBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition) self.bottomBackgroundView.update(size: self.bottomBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset + sideInset, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: containerWidth, height: UIScreenPixel))) transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset + sideInset, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: containerWidth, height: UIScreenPixel)))
let itemContainerSize = CGSize(width: containerWidth, height: availableSize.height) let itemContainerSize = CGSize(width: itemsContainerWidth, height: availableSize.height)
let itemLayout = ItemLayout(containerSize: itemContainerSize, containerInset: containerInset, bottomInset: bottomPanelHeight, topInset: topInset, sideInset: sideInset, navigationHeight: navigationHeight, sections: sections) let itemLayout = ItemLayout(style: itemLayoutStyle, containerSize: itemContainerSize, containerInset: containerInset, bottomInset: bottomPanelHeight, topInset: topInset, sideInset: sideInset, navigationHeight: navigationHeight, sections: sections)
let previousItemLayout = self.itemLayout let previousItemLayout = self.itemLayout
self.itemLayout = itemLayout self.itemLayout = itemLayout
contentTransition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: CGSize(width: containerWidth, height: itemLayout.contentHeight))) contentTransition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: itemLayout.contentHeight)))
let scrollContentHeight = max(topInset + itemLayout.contentHeight + containerInset, availableSize.height - containerInset) let scrollContentHeight = max(topInset + itemLayout.contentHeight + containerInset, availableSize.height - containerInset)
@ -1208,7 +1510,7 @@ final class ShareWithPeersScreenComponent: Component {
transition.setPosition(view: self.backgroundView, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) transition.setPosition(view: self.backgroundView, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: CGPoint(x: containerSideInset, y: 0.0), size: CGSize(width: containerWidth, height: availableSize.height))) transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: CGPoint(x: containerSideInset, y: 0.0), size: CGSize(width: containerWidth, height: availableSize.height)))
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 10.0), size: CGSize(width: availableSize.width, height: availableSize.height - 10.0)) let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset + 10.0), size: CGSize(width: availableSize.width, height: availableSize.height - 10.0))
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
@ -1265,7 +1567,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
public final class StateContext { public final class StateContext {
public enum Subject: Equatable { public enum Subject: Equatable {
case stories case stories(editing: Bool)
case chats case chats
case contacts(EngineStoryPrivacy.Base) case contacts(EngineStoryPrivacy.Base)
case search(String) case search(String)
@ -1430,11 +1732,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
public var dismissed: () -> Void = {} public var dismissed: () -> Void = {}
public init(context: AccountContext, initialPrivacy: EngineStoryPrivacy, timeout: Int, stateContext: StateContext, completion: @escaping (EngineStoryPrivacy) -> Void, editCategory: @escaping (EngineStoryPrivacy) -> Void, secondaryAction: @escaping () -> Void = {}) { public init(
context: AccountContext,
initialPrivacy: EngineStoryPrivacy,
allowScreenshots: Bool = true,
pin: Bool = false,
stateContext: StateContext,
completion: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
) {
self.context = context self.context = context
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = [] var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
if case .stories = stateContext.subject { var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
if case let .stories(editing) = stateContext.subject {
categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( categoryItems.append(ShareWithPeersScreenComponent.CategoryItem(
id: .everyone, id: .everyone,
title: "Everyone", title: "Everyone",
@ -1482,17 +1793,30 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
iconColor: .violet, iconColor: .violet,
actionTitle: selectedContactsSubtitle actionTitle: selectedContactsSubtitle
)) ))
if !editing {
optionItems.append(ShareWithPeersScreenComponent.OptionItem(
id: .screenshot,
title: "Allow Screenshots"
))
optionItems.append(ShareWithPeersScreenComponent.OptionItem(
id: .pin,
title: "Keep on My Page"
))
}
} }
super.init(context: context, component: ShareWithPeersScreenComponent( super.init(context: context, component: ShareWithPeersScreenComponent(
context: context, context: context,
stateContext: stateContext, stateContext: stateContext,
initialPrivacy: initialPrivacy, initialPrivacy: initialPrivacy,
timeout: timeout, screenshot: allowScreenshots,
pin: pin,
categoryItems: categoryItems, categoryItems: categoryItems,
optionItems: optionItems,
completion: completion, completion: completion,
editCategory: editCategory, editCategory: editCategory
secondaryAction: secondaryAction
), navigationBarAppearance: .none, theme: .dark) ), navigationBarAppearance: .none, theme: .dark)
self.statusBar.statusBarStyle = .Ignore self.statusBar.statusBarStyle = .Ignore

View File

@ -2587,7 +2587,7 @@ public final class StoryItemSetContainerComponent: Component {
return return
} }
let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: .stories, initialPeerIds: Set(privacy.additionallyIncludePeers)) let stateContext = ShareWithPeersScreen.StateContext(context: context, subject: .stories(editing: true), initialPeerIds: Set(privacy.additionallyIncludePeers))
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else { guard let self else {
return return
@ -2595,9 +2595,8 @@ public final class StoryItemSetContainerComponent: Component {
let controller = ShareWithPeersScreen( let controller = ShareWithPeersScreen(
context: context, context: context,
initialPrivacy: privacy, initialPrivacy: privacy,
timeout: 86400,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] privacy in completion: { [weak self] privacy, _, _ in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
@ -2606,7 +2605,7 @@ public final class StoryItemSetContainerComponent: Component {
self.privacyController = nil self.privacyController = nil
self.updateIsProgressPaused() self.updateIsProgressPaused()
}, },
editCategory: { [weak self] privacy in editCategory: { [weak self] privacy, _, _ in
guard let self else { guard let self else {
return return
} }
@ -2644,9 +2643,8 @@ public final class StoryItemSetContainerComponent: Component {
let controller = ShareWithPeersScreen( let controller = ShareWithPeersScreen(
context: context, context: context,
initialPrivacy: privacy, initialPrivacy: privacy,
timeout: 86400,
stateContext: stateContext, stateContext: stateContext,
completion: { result in completion: { result, _, _ in
if case .closeFriends = privacy.base { if case .closeFriends = privacy.base {
let _ = context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start() let _ = context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start()
completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: [])) completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []))
@ -2654,7 +2652,7 @@ public final class StoryItemSetContainerComponent: Component {
completion(result) completion(result)
} }
}, },
editCategory: { _ in } editCategory: { _, _, _ in }
) )
controller.dismissed = { [weak self] in controller.dismissed = { [weak self] in
if let self { if let self {

View File

@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "SwitchComponent",
module_name = "SwitchComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/ContextUI:ContextUI",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ComponentFlow:ComponentFlow",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,78 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import TelegramPresentationData
public final class SwitchComponent: Component {
public typealias EnvironmentType = Empty
let tintColor: UIColor?
let value: Bool
let valueUpdated: (Bool) -> Void
public init(
tintColor: UIColor? = nil,
value: Bool,
valueUpdated: @escaping (Bool) -> Void
) {
self.tintColor = tintColor
self.value = value
self.valueUpdated = valueUpdated
}
public static func ==(lhs: SwitchComponent, rhs: SwitchComponent) -> Bool {
if lhs.tintColor != rhs.tintColor {
return false
}
if lhs.value != rhs.value {
return false
}
return true
}
public final class View: UIView {
private let switchView: UISwitch
private var component: SwitchComponent?
override init(frame: CGRect) {
self.switchView = UISwitch()
super.init(frame: frame)
self.addSubview(self.switchView)
self.switchView.addTarget(self, action: #selector(self.valueChanged(_:)), for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func valueChanged(_ sender: Any) {
self.component?.valueUpdated(self.switchView.isOn)
}
func update(component: SwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.component = component
self.switchView.tintColor = component.tintColor
self.switchView.setOn(component.value, animated: !transition.animation.isImmediate)
self.switchView.sizeToFit()
self.switchView.frame = CGRect(origin: .zero, size: self.switchView.frame.size)
return self.switchView.frame.size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "arrow_left.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -10,35 +10,35 @@ stream
/DeviceRGB CS /DeviceRGB CS
/DeviceRGB cs /DeviceRGB cs
q q
1.000000 0.000000 -0.000000 1.000000 4.335022 4.334961 cm 1.000000 0.000000 -0.000000 1.000000 4.170021 4.169979 cm
1.000000 1.000000 1.000000 scn 1.000000 1.000000 1.000000 scn
1.330000 10.665017 m 1.660000 10.830000 m
1.330000 15.820595 5.509422 20.000017 10.665000 20.000017 c 1.660000 15.894451 5.765549 20.000000 10.830000 20.000000 c
15.820578 20.000017 20.000000 15.820595 20.000000 10.665017 c 15.894451 20.000000 20.000000 15.894451 20.000000 10.830000 c
20.000000 7.511573 18.436378 4.723331 16.042154 3.033316 c 20.000000 7.821688 18.551388 5.151716 16.313671 3.479595 c
14.314164 10.809276 l 14.640235 11.010053 l
14.246551 11.113539 13.976685 11.330017 13.665000 11.330017 c 14.555845 11.389809 14.219020 11.660000 13.830000 11.660000 c
13.443334 11.330017 l 13.553333 11.660000 l
11.613684 16.818966 l 11.778684 16.983950 l
11.309785 17.730663 10.020216 17.730665 9.716317 16.818970 c 11.474785 17.895645 10.185216 17.895649 9.881317 16.983952 c
7.886667 11.330017 l 8.106667 11.660000 l
7.665000 11.330017 l 7.830000 11.660000 l
7.353315 11.330017 7.083449 11.113539 7.015836 10.809276 c 7.440980 11.660000 7.104155 11.389809 7.019765 11.010053 c
5.287845 3.033316 l 5.346330 3.479595 l
2.893622 4.723331 1.330000 7.511574 1.330000 10.665017 c 3.108612 5.151716 1.660000 7.821688 1.660000 10.830000 c
h h
13.131556 10.000017 m 13.164198 10.000000 m
14.839726 2.313250 l 14.815517 2.569059 l
13.583508 1.684090 12.165603 1.330017 10.665000 1.330017 c 13.610337 1.986547 12.258304 1.660000 10.830000 1.660000 c
9.164397 1.330017 7.746492 1.684090 6.490273 2.313250 c 9.401696 1.660000 8.049662 1.986547 6.844482 2.569059 c
8.198444 10.000017 l 8.495803 10.000000 l
13.131556 10.000017 l 13.164198 10.000000 l
h h
10.665000 21.330017 m 10.830000 21.660000 m
4.774883 21.330017 0.000000 16.555134 0.000000 10.665017 c 4.848756 21.660000 0.000000 16.811243 0.000000 10.830000 c
0.000000 4.774900 4.774883 0.000015 10.665000 0.000015 c 0.000000 4.848755 4.848756 0.000000 10.830000 0.000000 c
16.555117 0.000015 21.330002 4.774900 21.330002 10.665017 c 16.811245 0.000000 21.660000 4.848755 21.660000 10.830000 c
21.330002 16.555134 16.555117 21.330017 10.665000 21.330017 c 21.660000 16.811243 16.811245 21.660000 10.830000 21.660000 c
h h
f* f*
n n

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "ic_editor_tools.pdf", "filename" : "tools_30.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -0,0 +1,101 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.170021 4.169979 cm
1.000000 1.000000 1.000000 scn
5.830000 21.660000 m
6.288396 21.660000 6.660000 21.288397 6.660000 20.830000 c
6.660000 17.660000 l
20.830000 17.660000 l
21.288397 17.660000 21.660000 17.288397 21.660000 16.830000 c
21.660000 16.371603 21.288397 16.000000 20.830000 16.000000 c
6.660000 16.000000 l
6.660000 12.830000 l
6.660000 12.371604 6.288396 12.000000 5.830000 12.000000 c
5.371603 12.000000 5.000000 12.371604 5.000000 12.830000 c
5.000000 16.000000 l
0.830000 16.000000 l
0.371603 16.000000 0.000000 16.371603 0.000000 16.830000 c
0.000000 17.288397 0.371603 17.660000 0.830000 17.660000 c
5.000000 17.660000 l
5.000000 20.830000 l
5.000000 21.288397 5.371603 21.660000 5.830000 21.660000 c
h
20.830000 3.999998 m
21.288397 3.999998 21.660000 4.371601 21.660000 4.829998 c
21.660000 5.288395 21.288397 5.659998 20.830000 5.659998 c
16.660000 5.659998 l
16.660000 8.830000 l
16.660000 9.288396 16.288397 9.660000 15.830000 9.660000 c
15.371604 9.660000 15.000000 9.288396 15.000000 8.830000 c
15.000000 5.659998 l
0.830000 5.659998 l
0.371603 5.659998 0.000000 5.288395 0.000000 4.829998 c
0.000000 4.371601 0.371603 3.999998 0.830000 3.999998 c
15.000000 3.999998 l
15.000000 0.829998 l
15.000000 0.371603 15.371604 0.000000 15.830000 0.000000 c
16.288397 0.000000 16.660000 0.371603 16.660000 0.829998 c
16.660000 3.999998 l
20.830000 3.999998 l
h
f*
n
Q
endstream
endobj
3 0 obj
1452
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001542 00000 n
0000001565 00000 n
0000001738 00000 n
0000001812 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1871
%%EOF

View File

@ -14502,7 +14502,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, shouldDismissOnTouch: { point in let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, shouldDismissOnTouch: { point in
return .ignore return .ignore
}, openActiveTextItem: { [weak self] item, action in }, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -14595,7 +14595,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore return .ignore
}, openActiveTextItem: { [weak self] item, action in }, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -14709,7 +14709,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore return .ignore
}, openActiveTextItem: { [weak self] item, action in }, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -24,10 +24,8 @@ import LegacyMediaPickerUI
import LegacyCamera import LegacyCamera
import AvatarNode import AvatarNode
import LocalMediaResources import LocalMediaResources
import ShareWithPeersScreen
import ImageCompression import ImageCompression
import TextFormat import TextFormat
import UndoUI
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
private var presentationData: PresentationData private var presentationData: PresentationData
@ -369,7 +367,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
case let .image(image, dimensions): case let .image(image, dimensions):
if let imageData = compressImageToJPEG(image, quality: 0.7) { if let imageData = compressImageToJPEG(image, quality: 0.7) {
let entities = generateChatInputTextEntities(caption) let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, isForwardingDisabled: false, period: privacy.timeout, randomId: randomId) self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }
@ -392,7 +390,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let entities = generateChatInputTextEntities(caption) let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, isForwardingDisabled: false, period: privacy.timeout, randomId: randomId) self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }

View File

@ -132,7 +132,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private var arrowGradientNode: ASDisplayNode? private var arrowGradientNode: ASDisplayNode?
private let arrowNode: ASImageNode private let arrowNode: ASImageNode
private let arrowContainer: ASDisplayNode private let arrowContainer: ASDisplayNode
private let animatedStickerNode: AnimatedStickerNode private let animatedStickerNode: DefaultAnimatedStickerNodeImpl
private var downArrowsNode: DownArrowsIconNode? private var downArrowsNode: DownArrowsIconNode?
private var avatarNode: AvatarNode? private var avatarNode: AvatarNode?
private var avatarStoryIndicator: ComponentView<Empty>? private var avatarStoryIndicator: ComponentView<Empty>?
@ -374,9 +374,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
switch icon { switch icon {
case .none: case .none:
break break
case let .animation(animationName, _): case let .animation(animationName, _, animationTintColor):
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animatedStickerNode.automaticallyLoadFirstFrame = true self.animatedStickerNode.automaticallyLoadFirstFrame = true
self.animatedStickerNode.dynamicColor = animationTintColor
case .downArrows: case .downArrows:
self.downArrowsNode = DownArrowsIconNode() self.downArrowsNode = DownArrowsIconNode()
case let .peer(peer, _): case let .peer(peer, _):
@ -510,21 +511,25 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let contentInset: CGFloat = 11.0 let contentInset: CGFloat = 11.0
let contentVerticalInset: CGFloat = 8.0 let contentVerticalInset: CGFloat = 8.0
let animationSize: CGSize let animationSize: CGSize
let animationInset: CGFloat var animationInset: CGFloat = 0.0
let animationSpacing: CGFloat var animationSpacing: CGFloat = 0.0
var animationOffset: CGFloat = 0.0
switch self.icon { switch self.icon {
case .none: case .none:
animationSize = CGSize() animationSize = CGSize()
animationInset = 0.0
animationSpacing = 0.0
case .downArrows: case .downArrows:
animationSize = CGSize(width: 24.0, height: 32.0) animationSize = CGSize(width: 24.0, height: 32.0)
animationInset = (40.0 - animationSize.width) / 2.0 animationInset = (40.0 - animationSize.width) / 2.0
animationSpacing = 8.0 case let .animation(animationName, _, _):
case let .animation(animationName, _): if animationName == "premium_unlock" {
animationSize = CGSize(width: 34.0, height: 34.0)
} else {
animationSize = CGSize(width: 32.0, height: 32.0) animationSize = CGSize(width: 32.0, height: 32.0)
if animationName == "ChatListFoldersTooltip" { }
if animationName == "anim_autoremove_on" {
animationOffset = -3.0
} else if animationName == "ChatListFoldersTooltip" {
animationInset = (70.0 - animationSize.width) / 2.0 animationInset = (70.0 - animationSize.width) / 2.0
} else { } else {
animationInset = 0.0 animationInset = 0.0
@ -555,12 +560,14 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
var backgroundHeight: CGFloat var backgroundHeight: CGFloat
switch self.tooltipStyle { switch self.tooltipStyle {
case .default, .gradient, .customBlur: case .default, .gradient, .customBlur, .wide:
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0
case .light: case .light:
backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0) backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0)
} }
if self.actionButtonNode != nil { if case .wide = self.tooltipStyle {
backgroundHeight += 4.0
} else if self.actionButtonNode != nil {
backgroundHeight += 4.0 backgroundHeight += 4.0
} }
@ -668,7 +675,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
transition.updateFrame(node: actionButtonNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.width - actionSize.width - 16.0, y: floor((backgroundHeight - actionSize.height) / 2.0)), size: actionSize)) transition.updateFrame(node: actionButtonNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.width - actionSize.width - 16.0, y: floor((backgroundHeight - actionSize.height) / 2.0)), size: actionSize))
} }
let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: floorToScreenPixels((backgroundHeight - animationSize.height - animationInset * 2.0) / 2.0)), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)) let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: floorToScreenPixels((backgroundHeight - animationSize.height - animationInset * 2.0) / 2.0) + animationOffset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame) transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame)
self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)) self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
@ -802,7 +809,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let animationDelay: Double let animationDelay: Double
switch self.icon { switch self.icon {
case let .animation(_, delay): case let .animation(_, delay, _):
animationDelay = delay animationDelay = delay
case .none, .downArrows: case .none, .downArrows:
animationDelay = 0.0 animationDelay = 0.0
@ -815,14 +822,14 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
}) })
} }
func animateOut(completion: @escaping () -> Void) { func animateOut(inPlace: Bool, completion: @escaping () -> Void) {
switch self.location { switch self.location {
case .top, .bottom: case .top, .bottom:
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { _ in self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { _ in
completion() completion()
}) })
self.containerNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.containerNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
if let _ = self.validLayout, case .top = self.location { if let _ = self.validLayout, case .top = self.location, !inPlace {
let offset: CGFloat let offset: CGFloat
if case .top = self.location { if case .top = self.location {
offset = -13.0 - self.backgroundContainerNode.frame.height offset = -13.0 - self.backgroundContainerNode.frame.height
@ -884,7 +891,7 @@ public final class TooltipScreen: ViewController {
} }
public enum Icon { public enum Icon {
case animation(name: String, delay: Double) case animation(name: String, delay: Double, tintColor: UIColor?)
case peer(peer: EnginePeer, isStory: Bool) case peer(peer: EnginePeer, isStory: Bool)
case downArrows case downArrows
} }
@ -918,6 +925,7 @@ public final class TooltipScreen: ViewController {
case light case light
case customBlur(UIColor) case customBlur(UIColor)
case gradient(UIColor, UIColor) case gradient(UIColor, UIColor)
case wide
} }
public enum Alignment { public enum Alignment {
@ -1073,13 +1081,13 @@ public final class TooltipScreen: ViewController {
self.controllerNode.addRelativeScrollingOffset(value, transition: transition) self.controllerNode.addRelativeScrollingOffset(value, transition: transition)
} }
override public func dismiss(completion: (() -> Void)? = nil) { public func dismiss(inPlace: Bool, completion: (() -> Void)? = nil) {
if self.isDismissed { if self.isDismissed {
return return
} }
self.isDismissed = true self.isDismissed = true
self.willBecomeDismissed?(self) self.willBecomeDismissed?(self)
self.controllerNode.animateOut(completion: { [weak self] in self.controllerNode.animateOut(inPlace: inPlace, completion: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1088,4 +1096,8 @@ public final class TooltipScreen: ViewController {
becameDismissed?(strongSelf) becameDismissed?(strongSelf)
}) })
} }
override public func dismiss(completion: (() -> Void)? = nil) {
self.dismiss(inPlace: false, completion: completion)
}
} }