mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 11:25:38 +00:00
Merge branch 'master' into temp-10
This commit is contained in:
commit
d1817dca18
@ -1200,6 +1200,11 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
.opacity(controlsAreVisible ? 1.0 : 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var additionalBottomInset: CGFloat = 0.0
|
||||||
|
if component.sourceHint == .storyEditor {
|
||||||
|
additionalBottomInset = max(0.0, previewBottomInset - environment.safeInsets.bottom - 49.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let textEntity = state.selectedEntity as? DrawingTextEntity {
|
if let textEntity = state.selectedEntity as? DrawingTextEntity {
|
||||||
let textSettings = textSettings.update(
|
let textSettings = textSettings.update(
|
||||||
component: TextSettingsComponent(
|
component: TextSettingsComponent(
|
||||||
@ -1277,7 +1282,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(textSettings
|
context.add(textSettings
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - textSettings.size.height / 2.0 - 89.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - textSettings.size.height / 2.0 - 89.0 - additionalBottomInset))
|
||||||
.appear(Transition.Appear({ _, view, transition in
|
.appear(Transition.Appear({ _, view, transition in
|
||||||
if let view = view as? TextSettingsComponent.View, !transition.animation.isImmediate {
|
if let view = view as? TextSettingsComponent.View, !transition.animation.isImmediate {
|
||||||
view.animateIn()
|
view.animateIn()
|
||||||
@ -1293,11 +1298,6 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var additionalBottomInset: CGFloat = 0.0
|
|
||||||
if component.sourceHint == .storyEditor {
|
|
||||||
additionalBottomInset = max(0.0, previewBottomInset - environment.safeInsets.bottom - 49.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let rightButtonPosition = rightEdge - 24.0
|
let rightButtonPosition = rightEdge - 24.0
|
||||||
var offsetX: CGFloat = leftEdge + 24.0
|
var offsetX: CGFloat = leftEdge + 24.0
|
||||||
let delta: CGFloat = (rightButtonPosition - offsetX) / 7.0
|
let delta: CGFloat = (rightButtonPosition - offsetX) / 7.0
|
||||||
|
|||||||
@ -361,7 +361,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[2107670217] = { return Api.InputPeer.parse_inputPeerSelf($0) }
|
dict[2107670217] = { return Api.InputPeer.parse_inputPeerSelf($0) }
|
||||||
dict[-571955892] = { return Api.InputPeer.parse_inputPeerUser($0) }
|
dict[-571955892] = { return Api.InputPeer.parse_inputPeerUser($0) }
|
||||||
dict[-1468331492] = { return Api.InputPeer.parse_inputPeerUserFromMessage($0) }
|
dict[-1468331492] = { return Api.InputPeer.parse_inputPeerUserFromMessage($0) }
|
||||||
dict[-551616469] = { return Api.InputPeerNotifySettings.parse_inputPeerNotifySettings($0) }
|
dict[-505078139] = { return Api.InputPeerNotifySettings.parse_inputPeerNotifySettings($0) }
|
||||||
dict[506920429] = { return Api.InputPhoneCall.parse_inputPhoneCall($0) }
|
dict[506920429] = { return Api.InputPhoneCall.parse_inputPhoneCall($0) }
|
||||||
dict[1001634122] = { return Api.InputPhoto.parse_inputPhoto($0) }
|
dict[1001634122] = { return Api.InputPhoto.parse_inputPhoto($0) }
|
||||||
dict[483901197] = { return Api.InputPhoto.parse_inputPhotoEmpty($0) }
|
dict[483901197] = { return Api.InputPhoto.parse_inputPhotoEmpty($0) }
|
||||||
@ -528,7 +528,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
|
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
|
||||||
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
|
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
|
||||||
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
|
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
|
||||||
dict[-946147823] = { return Api.MessageMedia.parse_messageMediaStory($0) }
|
dict[-877523576] = { return Api.MessageMedia.parse_messageMediaStory($0) }
|
||||||
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
|
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
|
||||||
dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) }
|
dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) }
|
||||||
dict[-1557277184] = { return Api.MessageMedia.parse_messageMediaWebPage($0) }
|
dict[-1557277184] = { return Api.MessageMedia.parse_messageMediaWebPage($0) }
|
||||||
@ -619,7 +619,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-386039788] = { return Api.PeerBlocked.parse_peerBlocked($0) }
|
dict[-386039788] = { return Api.PeerBlocked.parse_peerBlocked($0) }
|
||||||
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
|
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
|
||||||
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
|
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
|
||||||
dict[-1472527322] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
|
dict[1826385490] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
|
||||||
dict[-1525149427] = { return Api.PeerSettings.parse_peerSettings($0) }
|
dict[-1525149427] = { return Api.PeerSettings.parse_peerSettings($0) }
|
||||||
dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) }
|
dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) }
|
||||||
dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
|
dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
|
||||||
|
|||||||
@ -744,7 +744,7 @@ public extension Api {
|
|||||||
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
|
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
|
||||||
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
|
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
|
||||||
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
|
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
|
||||||
case messageMediaStory(userId: Int64, id: Int32)
|
case messageMediaStory(flags: Int32, userId: Int64, id: Int32, story: Api.StoryItem?)
|
||||||
case messageMediaUnsupported
|
case messageMediaUnsupported
|
||||||
case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
||||||
case messageMediaWebPage(webpage: Api.WebPage)
|
case messageMediaWebPage(webpage: Api.WebPage)
|
||||||
@ -834,12 +834,14 @@ public extension Api {
|
|||||||
poll.serialize(buffer, true)
|
poll.serialize(buffer, true)
|
||||||
results.serialize(buffer, true)
|
results.serialize(buffer, true)
|
||||||
break
|
break
|
||||||
case .messageMediaStory(let userId, let id):
|
case .messageMediaStory(let flags, let userId, let id, let story):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-946147823)
|
buffer.appendInt32(-877523576)
|
||||||
}
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt64(userId, buffer: buffer, boxed: false)
|
serializeInt64(userId, buffer: buffer, boxed: false)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
case .messageMediaUnsupported:
|
case .messageMediaUnsupported:
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -889,8 +891,8 @@ public extension Api {
|
|||||||
return ("messageMediaPhoto", [("flags", flags as Any), ("photo", photo as Any), ("ttlSeconds", ttlSeconds as Any)])
|
return ("messageMediaPhoto", [("flags", flags as Any), ("photo", photo as Any), ("ttlSeconds", ttlSeconds as Any)])
|
||||||
case .messageMediaPoll(let poll, let results):
|
case .messageMediaPoll(let poll, let results):
|
||||||
return ("messageMediaPoll", [("poll", poll as Any), ("results", results as Any)])
|
return ("messageMediaPoll", [("poll", poll as Any), ("results", results as Any)])
|
||||||
case .messageMediaStory(let userId, let id):
|
case .messageMediaStory(let flags, let userId, let id, let story):
|
||||||
return ("messageMediaStory", [("userId", userId as Any), ("id", id as Any)])
|
return ("messageMediaStory", [("flags", flags as Any), ("userId", userId as Any), ("id", id as Any), ("story", story as Any)])
|
||||||
case .messageMediaUnsupported:
|
case .messageMediaUnsupported:
|
||||||
return ("messageMediaUnsupported", [])
|
return ("messageMediaUnsupported", [])
|
||||||
case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType):
|
case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType):
|
||||||
@ -1092,14 +1094,22 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? {
|
public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? {
|
||||||
var _1: Int64?
|
var _1: Int32?
|
||||||
_1 = reader.readInt64()
|
_1 = reader.readInt32()
|
||||||
var _2: Int32?
|
var _2: Int64?
|
||||||
_2 = reader.readInt32()
|
_2 = reader.readInt64()
|
||||||
|
var _3: Int32?
|
||||||
|
_3 = reader.readInt32()
|
||||||
|
var _4: Api.StoryItem?
|
||||||
|
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_4 = Api.parse(reader, signature: signature) as? Api.StoryItem
|
||||||
|
} }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
if _c1 && _c2 {
|
let _c3 = _3 != nil
|
||||||
return Api.MessageMedia.messageMediaStory(userId: _1!, id: _2!)
|
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 {
|
||||||
|
return Api.MessageMedia.messageMediaStory(flags: _1!, userId: _2!, id: _3!, story: _4)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -754,13 +754,13 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum PeerNotifySettings: TypeConstructorDescription {
|
enum PeerNotifySettings: TypeConstructorDescription {
|
||||||
case peerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, iosSound: Api.NotificationSound?, androidSound: Api.NotificationSound?, otherSound: Api.NotificationSound?)
|
case peerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, iosSound: Api.NotificationSound?, androidSound: Api.NotificationSound?, otherSound: Api.NotificationSound?, storiesMuted: Api.Bool?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound):
|
case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound, let storiesMuted):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-1472527322)
|
buffer.appendInt32(1826385490)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)}
|
||||||
@ -769,14 +769,15 @@ public extension Api {
|
|||||||
if Int(flags) & Int(1 << 3) != 0 {iosSound!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 3) != 0 {iosSound!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 4) != 0 {androidSound!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 4) != 0 {androidSound!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 5) != 0 {otherSound!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 5) != 0 {otherSound!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 6) != 0 {storiesMuted!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound):
|
case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound, let storiesMuted):
|
||||||
return ("peerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("iosSound", iosSound as Any), ("androidSound", androidSound as Any), ("otherSound", otherSound as Any)])
|
return ("peerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("iosSound", iosSound as Any), ("androidSound", androidSound as Any), ("otherSound", otherSound as Any), ("storiesMuted", storiesMuted as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,6 +806,10 @@ public extension Api {
|
|||||||
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
||||||
_7 = Api.parse(reader, signature: signature) as? Api.NotificationSound
|
_7 = Api.parse(reader, signature: signature) as? Api.NotificationSound
|
||||||
} }
|
} }
|
||||||
|
var _8: Api.Bool?
|
||||||
|
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_8 = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
} }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||||
@ -812,8 +817,9 @@ public extension Api {
|
|||||||
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
||||||
let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil
|
let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil
|
||||||
let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil
|
let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil
|
||||||
return Api.PeerNotifySettings.peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||||
|
return Api.PeerNotifySettings.peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -278,27 +278,28 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum InputPeerNotifySettings: TypeConstructorDescription {
|
enum InputPeerNotifySettings: TypeConstructorDescription {
|
||||||
case inputPeerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: Api.NotificationSound?)
|
case inputPeerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: Api.NotificationSound?, storiesMuted: Api.Bool?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound):
|
case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound, let storiesMuted):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-551616469)
|
buffer.appendInt32(-505078139)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 1) != 0 {silent!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 1) != 0 {silent!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(muteUntil!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(muteUntil!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 3) != 0 {sound!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 3) != 0 {sound!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 6) != 0 {storiesMuted!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound):
|
case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound, let storiesMuted):
|
||||||
return ("inputPeerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("sound", sound as Any)])
|
return ("inputPeerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("sound", sound as Any), ("storiesMuted", storiesMuted as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,13 +320,18 @@ public extension Api {
|
|||||||
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
||||||
_5 = Api.parse(reader, signature: signature) as? Api.NotificationSound
|
_5 = Api.parse(reader, signature: signature) as? Api.NotificationSound
|
||||||
} }
|
} }
|
||||||
|
var _6: Api.Bool?
|
||||||
|
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_6 = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
} }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||||
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||||
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil
|
||||||
return Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||||
|
return Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5, storiesMuted: _6)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -380,7 +380,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
}
|
}
|
||||||
case let .messageMediaDice(value, emoticon):
|
case let .messageMediaDice(value, emoticon):
|
||||||
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil)
|
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil)
|
||||||
case let .messageMediaStory(userId, id):
|
case let .messageMediaStory(_, userId, id, _):
|
||||||
return (TelegramMediaStory(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id)), nil, nil, nil)
|
return (TelegramMediaStory(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id)), nil, nil, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import TelegramApi
|
|||||||
extension TelegramPeerNotificationSettings {
|
extension TelegramPeerNotificationSettings {
|
||||||
convenience init(apiSettings: Api.PeerNotifySettings) {
|
convenience init(apiSettings: Api.PeerNotifySettings) {
|
||||||
switch apiSettings {
|
switch apiSettings {
|
||||||
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound):
|
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted):
|
||||||
let sound: Api.NotificationSound?
|
let sound: Api.NotificationSound?
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
sound = iosSound
|
sound = iosSound
|
||||||
@ -34,7 +34,13 @@ extension TelegramPeerNotificationSettings {
|
|||||||
} else {
|
} else {
|
||||||
displayPreviews = .default
|
displayPreviews = .default
|
||||||
}
|
}
|
||||||
self.init(muteState: muteState, messageSound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), displayPreviews: displayPreviews)
|
|
||||||
|
var storiesMutedValue: Bool?
|
||||||
|
if let storiesMuted = storiesMuted {
|
||||||
|
storiesMutedValue = storiesMuted == .boolTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(muteState: muteState, messageSound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), displayPreviews: displayPreviews, storiesMuted: storiesMutedValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import TelegramApi
|
|||||||
extension MessageNotificationSettings {
|
extension MessageNotificationSettings {
|
||||||
init(apiSettings: Api.PeerNotifySettings) {
|
init(apiSettings: Api.PeerNotifySettings) {
|
||||||
switch apiSettings {
|
switch apiSettings {
|
||||||
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound):
|
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted):
|
||||||
let sound: Api.NotificationSound?
|
let sound: Api.NotificationSound?
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
sound = iosSound
|
sound = iosSound
|
||||||
@ -19,7 +19,11 @@ extension MessageNotificationSettings {
|
|||||||
} else {
|
} else {
|
||||||
displayPreviews = true
|
displayPreviews = true
|
||||||
}
|
}
|
||||||
self = MessageNotificationSettings(enabled: muteUntil == 0, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault))
|
var storiesMutedValue: Bool?
|
||||||
|
if let storiesMuted = storiesMuted {
|
||||||
|
storiesMutedValue = storiesMuted == .boolTrue
|
||||||
|
}
|
||||||
|
self = MessageNotificationSettings(enabled: muteUntil == 0, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,7 +116,7 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|
|||||||
|> map { chats, users, channels, contactsJoinedMuted in
|
|> map { chats, users, channels, contactsJoinedMuted in
|
||||||
let chatsSettings: MessageNotificationSettings
|
let chatsSettings: MessageNotificationSettings
|
||||||
switch chats {
|
switch chats {
|
||||||
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound):
|
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted):
|
||||||
let sound: Api.NotificationSound?
|
let sound: Api.NotificationSound?
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
sound = iosSound
|
sound = iosSound
|
||||||
@ -136,12 +136,18 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|
|||||||
} else {
|
} else {
|
||||||
displayPreviews = true
|
displayPreviews = true
|
||||||
}
|
}
|
||||||
chatsSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault))
|
|
||||||
|
var storiesMutedValue: Bool?
|
||||||
|
if let storiesMuted = storiesMuted {
|
||||||
|
storiesMutedValue = storiesMuted == .boolTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
chatsSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
let userSettings: MessageNotificationSettings
|
let userSettings: MessageNotificationSettings
|
||||||
switch users {
|
switch users {
|
||||||
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound):
|
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted):
|
||||||
let sound: Api.NotificationSound?
|
let sound: Api.NotificationSound?
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
sound = iosSound
|
sound = iosSound
|
||||||
@ -161,12 +167,18 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|
|||||||
} else {
|
} else {
|
||||||
displayPreviews = true
|
displayPreviews = true
|
||||||
}
|
}
|
||||||
userSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault))
|
|
||||||
|
var storiesMutedValue: Bool?
|
||||||
|
if let storiesMuted = storiesMuted {
|
||||||
|
storiesMutedValue = storiesMuted == .boolTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
userSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
let channelSettings: MessageNotificationSettings
|
let channelSettings: MessageNotificationSettings
|
||||||
switch channels {
|
switch channels {
|
||||||
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound):
|
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted):
|
||||||
let sound: Api.NotificationSound?
|
let sound: Api.NotificationSound?
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
sound = iosSound
|
sound = iosSound
|
||||||
@ -186,7 +198,13 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|
|||||||
} else {
|
} else {
|
||||||
displayPreviews = true
|
displayPreviews = true
|
||||||
}
|
}
|
||||||
channelSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault))
|
|
||||||
|
var storiesMutedValue: Bool?
|
||||||
|
if let storiesMuted = storiesMuted {
|
||||||
|
storiesMutedValue = storiesMuted == .boolTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
channelSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GlobalNotificationSettingsSet(privateChats: userSettings, groupChats: chatsSettings, channels: channelSettings, contactsJoined: contactsJoinedMuted == .boolFalse)
|
return GlobalNotificationSettingsSet(privateChats: userSettings, groupChats: chatsSettings, channels: channelSettings, contactsJoined: contactsJoinedMuted == .boolFalse)
|
||||||
@ -209,7 +227,14 @@ private func apiInputPeerNotifySettings(_ settings: MessageNotificationSettings)
|
|||||||
if sound != nil {
|
if sound != nil {
|
||||||
flags |= (1 << 3)
|
flags |= (1 << 3)
|
||||||
}
|
}
|
||||||
return .inputPeerNotifySettings(flags: flags, showPreviews: settings.displayPreviews ? .boolTrue : .boolFalse, silent: nil, muteUntil: muteUntil, sound: sound)
|
|
||||||
|
var storiesMuted: Api.Bool?
|
||||||
|
if let storiesMutedValue = settings.storiesMuted {
|
||||||
|
flags |= (1 << 6)
|
||||||
|
storiesMuted = storiesMutedValue ? .boolTrue : .boolFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
return .inputPeerNotifySettings(flags: flags, showPreviews: settings.displayPreviews ? .boolTrue : .boolFalse, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func pushedNotificationSettings(network: Network, settings: GlobalNotificationSettingsSet) -> Signal<Void, NoError> {
|
private func pushedNotificationSettings(network: Network, settings: GlobalNotificationSettingsSet) -> Signal<Void, NoError> {
|
||||||
|
|||||||
@ -130,7 +130,13 @@ func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: Pe
|
|||||||
if sound != nil {
|
if sound != nil {
|
||||||
flags |= (1 << 3)
|
flags |= (1 << 3)
|
||||||
}
|
}
|
||||||
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound)
|
var storiesMuted: Api.Bool?
|
||||||
|
if let storiesMutedValue = settings.storiesMuted {
|
||||||
|
flags |= (1 << 6)
|
||||||
|
storiesMuted = storiesMutedValue ? .boolTrue : .boolFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted)
|
||||||
return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyForumTopic(peer: inputPeer, topMsgId: Int32(clamping: threadId)), settings: inputSettings))
|
return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyForumTopic(peer: inputPeer, topMsgId: Int32(clamping: threadId)), settings: inputSettings))
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
return .single(.boolFalse)
|
return .single(.boolFalse)
|
||||||
@ -173,7 +179,12 @@ func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: Pe
|
|||||||
if sound != nil {
|
if sound != nil {
|
||||||
flags |= (1 << 3)
|
flags |= (1 << 3)
|
||||||
}
|
}
|
||||||
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound)
|
var storiesMuted: Api.Bool?
|
||||||
|
if let storiesMutedValue = settings.storiesMuted {
|
||||||
|
flags |= (1 << 6)
|
||||||
|
storiesMuted = storiesMutedValue ? .boolTrue : .boolFalse
|
||||||
|
}
|
||||||
|
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted)
|
||||||
return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings))
|
return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings))
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
return .single(.boolFalse)
|
return .single(.boolFalse)
|
||||||
|
|||||||
@ -4,15 +4,17 @@ public struct MessageNotificationSettings: Codable, Equatable {
|
|||||||
public var enabled: Bool
|
public var enabled: Bool
|
||||||
public var displayPreviews: Bool
|
public var displayPreviews: Bool
|
||||||
public var sound: PeerMessageSound
|
public var sound: PeerMessageSound
|
||||||
|
public var storiesMuted: Bool?
|
||||||
|
|
||||||
public static var defaultSettings: MessageNotificationSettings {
|
public static var defaultSettings: MessageNotificationSettings {
|
||||||
return MessageNotificationSettings(enabled: true, displayPreviews: true, sound: defaultCloudPeerNotificationSound)
|
return MessageNotificationSettings(enabled: true, displayPreviews: true, sound: defaultCloudPeerNotificationSound, storiesMuted: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(enabled: Bool, displayPreviews: Bool, sound: PeerMessageSound) {
|
public init(enabled: Bool, displayPreviews: Bool, sound: PeerMessageSound, storiesMuted: Bool?) {
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.displayPreviews = displayPreviews
|
self.displayPreviews = displayPreviews
|
||||||
self.sound = sound
|
self.sound = sound
|
||||||
|
self.storiesMuted = storiesMuted
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -22,6 +24,8 @@ public struct MessageNotificationSettings: Codable, Equatable {
|
|||||||
self.displayPreviews = ((try? container.decode(Int32.self, forKey: "p")) ?? 0) != 0
|
self.displayPreviews = ((try? container.decode(Int32.self, forKey: "p")) ?? 0) != 0
|
||||||
|
|
||||||
self.sound = try PeerMessageSound.decodeInline(container)
|
self.sound = try PeerMessageSound.decodeInline(container)
|
||||||
|
|
||||||
|
self.storiesMuted = try? container.decodeIfPresent(Bool.self, forKey: "st")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -30,6 +34,7 @@ public struct MessageNotificationSettings: Codable, Equatable {
|
|||||||
try container.encode((self.enabled ? 1 : 0) as Int32, forKey: "e")
|
try container.encode((self.enabled ? 1 : 0) as Int32, forKey: "e")
|
||||||
try container.encode((self.displayPreviews ? 1 : 0) as Int32, forKey: "p")
|
try container.encode((self.displayPreviews ? 1 : 0) as Int32, forKey: "p")
|
||||||
try self.sound.encodeInline(&container)
|
try self.sound.encodeInline(&container)
|
||||||
|
try container.encodeIfPresent(self.storiesMuted, forKey: "st")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -392,9 +392,10 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
|
|||||||
public let muteState: PeerMuteState
|
public let muteState: PeerMuteState
|
||||||
public let messageSound: PeerMessageSound
|
public let messageSound: PeerMessageSound
|
||||||
public let displayPreviews: PeerNotificationDisplayPreviews
|
public let displayPreviews: PeerNotificationDisplayPreviews
|
||||||
|
public let storiesMuted: Bool?
|
||||||
|
|
||||||
public static var defaultSettings: TelegramPeerNotificationSettings {
|
public static var defaultSettings: TelegramPeerNotificationSettings {
|
||||||
return TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default)
|
return TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storiesMuted: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isRemovedFromTotalUnreadCount(`default`: Bool) -> Bool {
|
public func isRemovedFromTotalUnreadCount(`default`: Bool) -> Bool {
|
||||||
@ -416,16 +417,18 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(muteState: PeerMuteState, messageSound: PeerMessageSound, displayPreviews: PeerNotificationDisplayPreviews) {
|
public init(muteState: PeerMuteState, messageSound: PeerMessageSound, displayPreviews: PeerNotificationDisplayPreviews, storiesMuted: Bool?) {
|
||||||
self.muteState = muteState
|
self.muteState = muteState
|
||||||
self.messageSound = messageSound
|
self.messageSound = messageSound
|
||||||
self.displayPreviews = displayPreviews
|
self.displayPreviews = displayPreviews
|
||||||
|
self.storiesMuted = storiesMuted
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.muteState = PeerMuteState.decodeInline(decoder)
|
self.muteState = PeerMuteState.decodeInline(decoder)
|
||||||
self.messageSound = PeerMessageSound.decodeInline(decoder)
|
self.messageSound = PeerMessageSound.decodeInline(decoder)
|
||||||
self.displayPreviews = PeerNotificationDisplayPreviews.decodeInline(decoder)
|
self.displayPreviews = PeerNotificationDisplayPreviews.decodeInline(decoder)
|
||||||
|
self.storiesMuted = decoder.decodeOptionalBoolForKey("stm")
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -434,6 +437,7 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
|
|||||||
self.muteState = try container.decode(PeerMuteState.self, forKey: "muteState")
|
self.muteState = try container.decode(PeerMuteState.self, forKey: "muteState")
|
||||||
self.messageSound = try container.decode(PeerMessageSound.self, forKey: "messageSound")
|
self.messageSound = try container.decode(PeerMessageSound.self, forKey: "messageSound")
|
||||||
self.displayPreviews = try container.decode(PeerNotificationDisplayPreviews.self, forKey: "displayPreviews")
|
self.displayPreviews = try container.decode(PeerNotificationDisplayPreviews.self, forKey: "displayPreviews")
|
||||||
|
self.storiesMuted = try? container.decodeIfPresent(Bool.self, forKey: "stm")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -442,12 +446,18 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
|
|||||||
try container.encode(self.muteState, forKey: "muteState")
|
try container.encode(self.muteState, forKey: "muteState")
|
||||||
try container.encode(self.messageSound, forKey: "messageSound")
|
try container.encode(self.messageSound, forKey: "messageSound")
|
||||||
try container.encode(self.displayPreviews, forKey: "displayPreviews")
|
try container.encode(self.displayPreviews, forKey: "displayPreviews")
|
||||||
|
try container.encodeIfPresent(self.storiesMuted, forKey: "stm")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
self.muteState.encodeInline(encoder)
|
self.muteState.encodeInline(encoder)
|
||||||
self.messageSound.encodeInline(encoder)
|
self.messageSound.encodeInline(encoder)
|
||||||
self.displayPreviews.encodeInline(encoder)
|
self.displayPreviews.encodeInline(encoder)
|
||||||
|
if let storiesMuted = self.storiesMuted {
|
||||||
|
encoder.encodeBool(storiesMuted, forKey: "stm")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "stm")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isEqual(to: PeerNotificationSettings) -> Bool {
|
public func isEqual(to: PeerNotificationSettings) -> Bool {
|
||||||
@ -459,18 +469,22 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedMuteState(_ muteState: PeerMuteState) -> TelegramPeerNotificationSettings {
|
public func withUpdatedMuteState(_ muteState: PeerMuteState) -> TelegramPeerNotificationSettings {
|
||||||
return TelegramPeerNotificationSettings(muteState: muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews)
|
return TelegramPeerNotificationSettings(muteState: muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews, storiesMuted: self.storiesMuted)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedMessageSound(_ messageSound: PeerMessageSound) -> TelegramPeerNotificationSettings {
|
public func withUpdatedMessageSound(_ messageSound: PeerMessageSound) -> TelegramPeerNotificationSettings {
|
||||||
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: messageSound, displayPreviews: self.displayPreviews)
|
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: messageSound, displayPreviews: self.displayPreviews, storiesMuted: self.storiesMuted)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedDisplayPreviews(_ displayPreviews: PeerNotificationDisplayPreviews) -> TelegramPeerNotificationSettings {
|
public func withUpdatedDisplayPreviews(_ displayPreviews: PeerNotificationDisplayPreviews) -> TelegramPeerNotificationSettings {
|
||||||
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: displayPreviews)
|
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: displayPreviews, storiesMuted: self.storiesMuted)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func withUpdatedStoriesMuted(_ storiesMuted: Bool?) -> TelegramPeerNotificationSettings {
|
||||||
|
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews, storiesMuted: storiesMuted)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: TelegramPeerNotificationSettings, rhs: TelegramPeerNotificationSettings) -> Bool {
|
public static func ==(lhs: TelegramPeerNotificationSettings, rhs: TelegramPeerNotificationSettings) -> Bool {
|
||||||
return lhs.muteState == rhs.muteState && lhs.messageSound == rhs.messageSound && lhs.displayPreviews == rhs.displayPreviews
|
return lhs.muteState == rhs.muteState && lhs.messageSound == rhs.messageSound && lhs.displayPreviews == rhs.displayPreviews && lhs.storiesMuted == rhs.storiesMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,15 +88,18 @@ public enum EnginePeer: Equatable {
|
|||||||
public var muteState: MuteState
|
public var muteState: MuteState
|
||||||
public var messageSound: MessageSound
|
public var messageSound: MessageSound
|
||||||
public var displayPreviews: DisplayPreviews
|
public var displayPreviews: DisplayPreviews
|
||||||
|
public var storiesMuted: Bool?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
muteState: MuteState,
|
muteState: MuteState,
|
||||||
messageSound: MessageSound,
|
messageSound: MessageSound,
|
||||||
displayPreviews: DisplayPreviews
|
displayPreviews: DisplayPreviews,
|
||||||
|
storiesMuted: Bool?
|
||||||
) {
|
) {
|
||||||
self.muteState = muteState
|
self.muteState = muteState
|
||||||
self.messageSound = messageSound
|
self.messageSound = messageSound
|
||||||
self.displayPreviews = displayPreviews
|
self.displayPreviews = displayPreviews
|
||||||
|
self.storiesMuted = storiesMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,11 +219,13 @@ public struct EngineGlobalNotificationSettings: Equatable {
|
|||||||
public var enabled: Bool
|
public var enabled: Bool
|
||||||
public var displayPreviews: Bool
|
public var displayPreviews: Bool
|
||||||
public var sound: EnginePeer.NotificationSettings.MessageSound
|
public var sound: EnginePeer.NotificationSettings.MessageSound
|
||||||
|
public var storiesMuted: Bool
|
||||||
|
|
||||||
public init(enabled: Bool, displayPreviews: Bool, sound: EnginePeer.NotificationSettings.MessageSound) {
|
public init(enabled: Bool, displayPreviews: Bool, sound: EnginePeer.NotificationSettings.MessageSound, storiesMuted: Bool) {
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.displayPreviews = displayPreviews
|
self.displayPreviews = displayPreviews
|
||||||
self.sound = sound
|
self.sound = sound
|
||||||
|
self.storiesMuted = storiesMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +332,8 @@ public extension EnginePeer.NotificationSettings {
|
|||||||
self.init(
|
self.init(
|
||||||
muteState: MuteState(notificationSettings.muteState),
|
muteState: MuteState(notificationSettings.muteState),
|
||||||
messageSound: MessageSound(notificationSettings.messageSound),
|
messageSound: MessageSound(notificationSettings.messageSound),
|
||||||
displayPreviews: DisplayPreviews(notificationSettings.displayPreviews)
|
displayPreviews: DisplayPreviews(notificationSettings.displayPreviews),
|
||||||
|
storiesMuted: notificationSettings.storiesMuted
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +341,8 @@ public extension EnginePeer.NotificationSettings {
|
|||||||
return TelegramPeerNotificationSettings(
|
return TelegramPeerNotificationSettings(
|
||||||
muteState: self.muteState._asMuteState(),
|
muteState: self.muteState._asMuteState(),
|
||||||
messageSound: self.messageSound._asMessageSound(),
|
messageSound: self.messageSound._asMessageSound(),
|
||||||
displayPreviews: self.displayPreviews._asDisplayPreviews()
|
displayPreviews: self.displayPreviews._asDisplayPreviews(),
|
||||||
|
storiesMuted: self.storiesMuted
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -594,7 +601,8 @@ public extension EngineGlobalNotificationSettings.CategorySettings {
|
|||||||
self.init(
|
self.init(
|
||||||
enabled: categorySettings.enabled,
|
enabled: categorySettings.enabled,
|
||||||
displayPreviews: categorySettings.displayPreviews,
|
displayPreviews: categorySettings.displayPreviews,
|
||||||
sound: EnginePeer.NotificationSettings.MessageSound(categorySettings.sound)
|
sound: EnginePeer.NotificationSettings.MessageSound(categorySettings.sound),
|
||||||
|
storiesMuted: categorySettings.storiesMuted ?? false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,7 +610,8 @@ public extension EngineGlobalNotificationSettings.CategorySettings {
|
|||||||
return MessageNotificationSettings(
|
return MessageNotificationSettings(
|
||||||
enabled: self.enabled,
|
enabled: self.enabled,
|
||||||
displayPreviews: self.displayPreviews,
|
displayPreviews: self.displayPreviews,
|
||||||
sound: self.sound._asMessageSound()
|
sound: self.sound._asMessageSound(),
|
||||||
|
storiesMuted: self.storiesMuted
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,6 +73,7 @@ swift_library(
|
|||||||
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||||
"//submodules/TooltipUI",
|
"//submodules/TooltipUI",
|
||||||
"//submodules/TelegramUI/Components/MediaEditor",
|
"//submodules/TelegramUI/Components/MediaEditor",
|
||||||
|
"//submodules/Components/MetalImageView:MetalImageView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -148,6 +148,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
var cameraState = CameraState(mode: .photo, position: .unspecified, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0)
|
var cameraState = CameraState(mode: .photo, position: .unspecified, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0)
|
||||||
var swipeHint: CaptureControlsComponent.SwipeHint = .none
|
var swipeHint: CaptureControlsComponent.SwipeHint = .none
|
||||||
|
var isTransitioning = false
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
@ -267,6 +268,12 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
self.completion.invoke(.single(.video(path, transitionImage, PixelDimensions(width: 1080, height: 1920))))
|
self.completion.invoke(.single(.video(path, transitionImage, PixelDimensions(width: 1080, height: 1920))))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
self.isTransitioning = true
|
||||||
|
Queue.mainQueue().after(0.8, {
|
||||||
|
self.isTransitioning = false
|
||||||
|
self.updated(transition: .immediate)
|
||||||
|
})
|
||||||
|
|
||||||
self.updated(transition: .spring(duration: 0.4))
|
self.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +297,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let zoomControl = Child(ZoomComponent.self)
|
let zoomControl = Child(ZoomComponent.self)
|
||||||
let flashButton = Child(CameraButton.self)
|
let flashButton = Child(CameraButton.self)
|
||||||
let modeControl = Child(ModeComponent.self)
|
let modeControl = Child(ModeComponent.self)
|
||||||
let hintLabel = Child(MultilineTextComponent.self)
|
let hintLabel = Child(HintLabelComponent.self)
|
||||||
|
|
||||||
let timeBackground = Child(RoundedRectangle.self)
|
let timeBackground = Child(RoundedRectangle.self)
|
||||||
let timeLabel = Child(MultilineTextComponent.self)
|
let timeLabel = Child(MultilineTextComponent.self)
|
||||||
@ -308,7 +315,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
state?.updateCameraMode(mode)
|
state?.updateCameraMode(mode)
|
||||||
})
|
})
|
||||||
|
|
||||||
if case .none = state.cameraState.recording {
|
if case .none = state.cameraState.recording, !state.isTransitioning {
|
||||||
let cancelButton = cancelButton.update(
|
let cancelButton = cancelButton.update(
|
||||||
component: CameraButton(
|
component: CameraButton(
|
||||||
content: AnyComponentWithIdentity(
|
content: AnyComponentWithIdentity(
|
||||||
@ -420,17 +427,21 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let shutterState: ShutterButtonState
|
let shutterState: ShutterButtonState
|
||||||
switch state.cameraState.recording {
|
if state.isTransitioning {
|
||||||
case .handsFree:
|
shutterState = .transition
|
||||||
shutterState = .stopRecording
|
} else {
|
||||||
case .holding:
|
switch state.cameraState.recording {
|
||||||
shutterState = .holdRecording(progress: min(1.0, Float(state.cameraState.duration / 60.0)))
|
case .handsFree:
|
||||||
case .none:
|
shutterState = .stopRecording
|
||||||
switch state.cameraState.mode {
|
case .holding:
|
||||||
case .photo:
|
shutterState = .holdRecording(progress: min(1.0, Float(state.cameraState.duration / 60.0)))
|
||||||
shutterState = .generic
|
case .none:
|
||||||
case .video:
|
switch state.cameraState.mode {
|
||||||
shutterState = .video
|
case .photo:
|
||||||
|
shutterState = .generic
|
||||||
|
case .video:
|
||||||
|
shutterState = .video
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +516,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
isVideoRecording = true
|
isVideoRecording = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isVideoRecording {
|
if isVideoRecording && !state.isTransitioning {
|
||||||
let duration = Int(state.cameraState.duration)
|
let duration = Int(state.cameraState.duration)
|
||||||
let durationString = String(format: "%02d:%02d", (duration / 60) % 60, duration % 60)
|
let durationString = String(format: "%02d:%02d", (duration / 60) % 60, duration % 60)
|
||||||
let timeLabel = timeLabel.update(
|
let timeLabel = timeLabel.update(
|
||||||
@ -541,7 +552,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let hintText: String?
|
let hintText: String?
|
||||||
switch state.swipeHint {
|
switch state.swipeHint {
|
||||||
case .none:
|
case .none:
|
||||||
hintText = nil
|
hintText = " "
|
||||||
case .zoom:
|
case .zoom:
|
||||||
hintText = "Swipe up to zoom"
|
hintText = "Swipe up to zoom"
|
||||||
case .lock:
|
case .lock:
|
||||||
@ -553,10 +564,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
if let hintText {
|
if let hintText {
|
||||||
let hintLabel = hintLabel.update(
|
let hintLabel = hintLabel.update(
|
||||||
component: MultilineTextComponent(
|
component: HintLabelComponent(text: hintText),
|
||||||
text: .plain(NSAttributedString(string: hintText.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: .white)),
|
|
||||||
horizontalAlignment: .center
|
|
||||||
),
|
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
@ -569,7 +577,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .none = state.cameraState.recording {
|
if case .none = state.cameraState.recording, !state.isTransitioning {
|
||||||
let modeControl = modeControl.update(
|
let modeControl = modeControl.update(
|
||||||
component: ModeComponent(
|
component: ModeComponent(
|
||||||
availableModes: [.photo, .video],
|
availableModes: [.photo, .video],
|
||||||
@ -878,6 +886,7 @@ public class CameraScreen: ViewController {
|
|||||||
self.effectivePreviewView.addGestureRecognizer(pinchGestureRecognizer)
|
self.effectivePreviewView.addGestureRecognizer(pinchGestureRecognizer)
|
||||||
|
|
||||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
|
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
|
||||||
|
panGestureRecognizer.maximumNumberOfTouches = 1
|
||||||
self.effectivePreviewView.addGestureRecognizer(panGestureRecognizer)
|
self.effectivePreviewView.addGestureRecognizer(panGestureRecognizer)
|
||||||
|
|
||||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
|
||||||
|
|||||||
@ -11,6 +11,7 @@ enum ShutterButtonState: Equatable {
|
|||||||
case video
|
case video
|
||||||
case stopRecording
|
case stopRecording
|
||||||
case holdRecording(progress: Float)
|
case holdRecording(progress: Float)
|
||||||
|
case transition
|
||||||
}
|
}
|
||||||
|
|
||||||
private let maximumShutterSize = CGSize(width: 96.0, height: 96.0)
|
private let maximumShutterSize = CGSize(width: 96.0, height: 96.0)
|
||||||
@ -141,6 +142,12 @@ private final class ShutterButtonContentComponent: Component {
|
|||||||
innerCornerRadius = innerSize.height / 2.0
|
innerCornerRadius = innerSize.height / 2.0
|
||||||
ringSize = CGSize(width: 92.0, height: 92.0)
|
ringSize = CGSize(width: 92.0, height: 92.0)
|
||||||
recordingProgress = progress
|
recordingProgress = progress
|
||||||
|
case .transition:
|
||||||
|
innerColor = videoRedColor
|
||||||
|
innerSize = CGSize(width: 60.0, height: 60.0)
|
||||||
|
innerCornerRadius = innerSize.height / 2.0
|
||||||
|
ringSize = CGSize(width: 68.0, height: 68.0)
|
||||||
|
recordingProgress = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ringLayer.fillColor = UIColor.clear.cgColor
|
self.ringLayer.fillColor = UIColor.clear.cgColor
|
||||||
@ -573,6 +580,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
let buttonSideInset: CGFloat = 28.0
|
let buttonSideInset: CGFloat = 28.0
|
||||||
//let buttonMaxOffset: CGFloat = 100.0
|
//let buttonMaxOffset: CGFloat = 100.0
|
||||||
|
|
||||||
|
var isTransitioning = false
|
||||||
var isRecording = false
|
var isRecording = false
|
||||||
var isHolding = false
|
var isHolding = false
|
||||||
if case .stopRecording = component.shutterState {
|
if case .stopRecording = component.shutterState {
|
||||||
@ -580,6 +588,8 @@ final class CaptureControlsComponent: Component {
|
|||||||
} else if case .holdRecording = component.shutterState {
|
} else if case .holdRecording = component.shutterState {
|
||||||
isRecording = true
|
isRecording = true
|
||||||
isHolding = true
|
isHolding = true
|
||||||
|
} else if case .transition = component.shutterState {
|
||||||
|
isTransitioning = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let galleryButtonSize = self.galleryButtonView.update(
|
let galleryButtonSize = self.galleryButtonView.update(
|
||||||
@ -615,8 +625,8 @@ final class CaptureControlsComponent: Component {
|
|||||||
transition.setBounds(view: galleryButtonView, bounds: CGRect(origin: .zero, size: galleryButtonFrame.size))
|
transition.setBounds(view: galleryButtonView, bounds: CGRect(origin: .zero, size: galleryButtonFrame.size))
|
||||||
transition.setPosition(view: galleryButtonView, position: galleryButtonFrame.center)
|
transition.setPosition(view: galleryButtonView, position: galleryButtonFrame.center)
|
||||||
|
|
||||||
transition.setScale(view: galleryButtonView, scale: isRecording ? 0.1 : 1.0)
|
transition.setScale(view: galleryButtonView, scale: isRecording || isTransitioning ? 0.1 : 1.0)
|
||||||
transition.setAlpha(view: galleryButtonView, alpha: isRecording ? 0.0 : 1.0)
|
transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.lockView.update(
|
let _ = self.lockView.update(
|
||||||
@ -678,13 +688,16 @@ final class CaptureControlsComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setBounds(view: flipButtonView, bounds: CGRect(origin: .zero, size: flipButtonFrame.size))
|
transition.setBounds(view: flipButtonView, bounds: CGRect(origin: .zero, size: flipButtonFrame.size))
|
||||||
transition.setPosition(view: flipButtonView, position: flipButtonFrame.center)
|
transition.setPosition(view: flipButtonView, position: flipButtonFrame.center)
|
||||||
|
|
||||||
|
transition.setScale(view: flipButtonView, scale: isTransitioning ? 0.01 : 1.0)
|
||||||
|
transition.setAlpha(view: flipButtonView, alpha: isTransitioning ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var blobState: ShutterBlobView.BlobState
|
var blobState: ShutterBlobView.BlobState
|
||||||
switch component.shutterState {
|
switch component.shutterState {
|
||||||
case .generic:
|
case .generic:
|
||||||
blobState = .generic
|
blobState = .generic
|
||||||
case .video:
|
case .video, .transition:
|
||||||
blobState = .video
|
blobState = .video
|
||||||
case .stopRecording:
|
case .stopRecording:
|
||||||
blobState = .stopVideo
|
blobState = .stopVideo
|
||||||
@ -732,6 +745,8 @@ final class CaptureControlsComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setBounds(view: shutterButtonView, bounds: CGRect(origin: .zero, size: shutterButtonFrame.size))
|
transition.setBounds(view: shutterButtonView, bounds: CGRect(origin: .zero, size: shutterButtonFrame.size))
|
||||||
transition.setPosition(view: shutterButtonView, position: shutterButtonFrame.center)
|
transition.setPosition(view: shutterButtonView, position: shutterButtonFrame.center)
|
||||||
|
transition.setScale(view: shutterButtonView, scale: isTransitioning ? 0.01 : 1.0)
|
||||||
|
transition.setAlpha(view: shutterButtonView, alpha: isTransitioning ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let guideSpacing: CGFloat = 9.0
|
let guideSpacing: CGFloat = 9.0
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import MultilineTextComponent
|
||||||
|
|
||||||
extension CameraMode {
|
extension CameraMode {
|
||||||
var title: String {
|
var title: String {
|
||||||
@ -161,3 +162,83 @@ final class ModeComponent: Component {
|
|||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class HintLabelComponent: Component {
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
text: String
|
||||||
|
) {
|
||||||
|
self.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: HintLabelComponent, rhs: HintLabelComponent) -> Bool {
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView {
|
||||||
|
private var component: HintLabelComponent?
|
||||||
|
private var componentView = ComponentView<Empty>()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: HintLabelComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
|
let previousComponent = self.component
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
if let previousText = previousComponent?.text, !previousText.isEmpty && previousText != component.text {
|
||||||
|
if let componentView = self.componentView.view, let snapshotView = componentView.snapshotView(afterScreenUpdates: false) {
|
||||||
|
snapshotView.frame = componentView.frame
|
||||||
|
self.addSubview(snapshotView)
|
||||||
|
snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
self.componentView.view?.removeFromSuperview()
|
||||||
|
self.componentView = ComponentView<Empty>()
|
||||||
|
}
|
||||||
|
|
||||||
|
let textSize = self.componentView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: component.text.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: .white)),
|
||||||
|
horizontalAlignment: .center
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
if let view = self.componentView.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - textSize.width) / 2.0), y: 0.0), size: textSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CGSize(width: availableSize.width, height: textSize.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import Metal
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import Display
|
import Display
|
||||||
|
import MetalImageView
|
||||||
|
|
||||||
private final class PropertyAnimation<T: Interpolatable> {
|
private final class PropertyAnimation<T: Interpolatable> {
|
||||||
let from: T
|
let from: T
|
||||||
@ -63,6 +64,7 @@ private final class AnimatableProperty<T: Interpolatable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tick(timestamp: Double) -> Bool {
|
func tick(timestamp: Double) -> Bool {
|
||||||
|
|
||||||
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -73,8 +75,7 @@ private final class AnimatableProperty<T: Interpolatable> {
|
|||||||
case .easeInOut:
|
case .easeInOut:
|
||||||
t = listViewAnimationCurveEaseInOut(t)
|
t = listViewAnimationCurveEaseInOut(t)
|
||||||
case .spring:
|
case .spring:
|
||||||
t = listViewAnimationCurveEaseInOut(t)
|
t = lookupSpringValue(t)
|
||||||
//t = listViewAnimationCurveSystem(t)
|
|
||||||
case let .custom(x1, y1, x2, y2):
|
case let .custom(x1, y1, x2, y2):
|
||||||
t = bezierPoint(CGFloat(x1), CGFloat(y1), CGFloat(x2), CGFloat(y2), t)
|
t = bezierPoint(CGFloat(x1), CGFloat(y1), CGFloat(x2), CGFloat(y2), t)
|
||||||
}
|
}
|
||||||
@ -88,7 +89,69 @@ private final class AnimatableProperty<T: Interpolatable> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ShutterBlobView: MTKView, MTKViewDelegate {
|
private func lookupSpringValue(_ t: CGFloat) -> CGFloat {
|
||||||
|
let table: [(CGFloat, CGFloat)] = [
|
||||||
|
(0.0, 0.0),
|
||||||
|
(0.0625, 0.1123005598783493),
|
||||||
|
(0.125, 0.31598418951034546),
|
||||||
|
(0.1875, 0.5103585720062256),
|
||||||
|
(0.25, 0.6650152802467346),
|
||||||
|
(0.3125, 0.777747631072998),
|
||||||
|
(0.375, 0.8557760119438171),
|
||||||
|
(0.4375, 0.9079672694206238),
|
||||||
|
(0.5, 0.942038357257843),
|
||||||
|
(0.5625, 0.9638798832893372),
|
||||||
|
(0.625, 0.9776856303215027),
|
||||||
|
(0.6875, 0.9863143563270569),
|
||||||
|
(0.75, 0.991658091545105),
|
||||||
|
(0.8125, 0.9949421286582947),
|
||||||
|
(0.875, 0.9969474077224731),
|
||||||
|
(0.9375, 0.9981651306152344),
|
||||||
|
(1.0, 1.0)
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in 0 ..< table.count - 2 {
|
||||||
|
let lhs = table[i]
|
||||||
|
let rhs = table[i + 1]
|
||||||
|
|
||||||
|
if t >= lhs.0 && t <= rhs.0 {
|
||||||
|
let fraction = (t - lhs.0) / (rhs.0 - lhs.0)
|
||||||
|
let value = lhs.1 + fraction * (rhs.1 - lhs.1)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0
|
||||||
|
// print("---start---")
|
||||||
|
// for i in 0 ..< 16 {
|
||||||
|
// let j = Double(i) * 1.0 / 16.0
|
||||||
|
// print("\(j) \(listViewAnimationCurveSystem(j))")
|
||||||
|
// }
|
||||||
|
// print("---end---")
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ShutterBlobLayer: MetalImageLayer {
|
||||||
|
override public init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.renderer.imageUpdated = { [weak self] image in
|
||||||
|
self?.contents = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(layer: Any) {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
if let layer = layer as? ShutterBlobLayer {
|
||||||
|
self.contents = layer.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ShutterBlobView: UIView {
|
||||||
enum BlobState {
|
enum BlobState {
|
||||||
case generic
|
case generic
|
||||||
case video
|
case video
|
||||||
@ -147,7 +210,6 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
|
|
||||||
private let commandQueue: MTLCommandQueue
|
private let commandQueue: MTLCommandQueue
|
||||||
private let drawPassthroughPipelineState: MTLRenderPipelineState
|
private let drawPassthroughPipelineState: MTLRenderPipelineState
|
||||||
private var viewportDimensions = CGSize(width: 1, height: 1)
|
|
||||||
|
|
||||||
private var displayLink: SharedDisplayLinkDriver.Link?
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
||||||
|
|
||||||
@ -162,6 +224,10 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
|
|
||||||
private(set) var state: BlobState = .generic
|
private(set) var state: BlobState = .generic
|
||||||
|
|
||||||
|
static override var layerClass: AnyClass {
|
||||||
|
return ShutterBlobLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
public init?(test: Bool) {
|
public init?(test: Bool) {
|
||||||
let mainBundle = Bundle(for: ShutterBlobView.self)
|
let mainBundle = Bundle(for: ShutterBlobView.self)
|
||||||
|
|
||||||
@ -207,16 +273,12 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
|
|
||||||
self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
|
self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
|
||||||
|
|
||||||
super.init(frame: CGRect(), device: device)
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
(self.layer as! ShutterBlobLayer).renderer.device = device
|
||||||
|
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
self.backgroundColor = .clear
|
self.backgroundColor = .clear
|
||||||
|
|
||||||
self.colorPixelFormat = .bgra8Unorm
|
|
||||||
self.framebufferOnly = true
|
|
||||||
|
|
||||||
self.isPaused = true
|
|
||||||
self.delegate = self
|
|
||||||
|
|
||||||
self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in
|
self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in
|
||||||
self?.tick()
|
self?.tick()
|
||||||
@ -232,10 +294,6 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
self.displayLink?.invalidate()
|
self.displayLink?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
|
|
||||||
self.viewportDimensions = size
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateState(_ state: BlobState, transition: Transition = .immediate) {
|
func updateState(_ state: BlobState, transition: Transition = .immediate) {
|
||||||
guard self.state != state else {
|
guard self.state != state else {
|
||||||
return
|
return
|
||||||
@ -297,40 +355,56 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
self.draw()
|
self.draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func draw(_ rect: CGRect) {
|
override func layoutSubviews() {
|
||||||
self.redraw(drawable: self.currentDrawable!)
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
self.tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func redraw(drawable: MTLDrawable) {
|
private func getNextDrawable(layer: MetalImageLayer, drawableSize: CGSize) -> MetalImageLayer.Drawable? {
|
||||||
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
|
layer.renderer.drawableSize = drawableSize
|
||||||
|
return layer.renderer.nextDrawable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func draw() {
|
||||||
|
guard let layer = self.layer as? MetalImageLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updateAnimations()
|
||||||
|
|
||||||
|
let drawableSize = CGSize(width: self.bounds.width * UIScreen.main.scale, height: self.bounds.height * UIScreen.main.scale)
|
||||||
|
|
||||||
|
guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let renderPassDescriptor = self.currentRenderPassDescriptor!
|
let renderPassDescriptor = MTLRenderPassDescriptor()
|
||||||
|
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
|
||||||
renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
||||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0.0)
|
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
|
||||||
|
|
||||||
|
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
|
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewportDimensions = self.viewportDimensions
|
renderEncoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0, width: drawableSize.width, height: drawableSize.height, znear: -1.0, zfar: 1.0))
|
||||||
renderEncoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0, width: viewportDimensions.width, height: viewportDimensions.height, znear: -1.0, zfar: 1.0))
|
|
||||||
renderEncoder.setRenderPipelineState(self.drawPassthroughPipelineState)
|
renderEncoder.setRenderPipelineState(self.drawPassthroughPipelineState)
|
||||||
|
|
||||||
let w = Float(1)
|
|
||||||
let h = Float(1)
|
|
||||||
var vertices: [Float] = [
|
var vertices: [Float] = [
|
||||||
w, -h,
|
1, -1,
|
||||||
-w, -h,
|
-1, -1,
|
||||||
-w, h,
|
-1, 1,
|
||||||
w, -h,
|
1, -1,
|
||||||
-w, h,
|
-1, 1,
|
||||||
w, h
|
1, 1
|
||||||
]
|
]
|
||||||
renderEncoder.setVertexBytes(&vertices, length: 4 * vertices.count, index: 0)
|
renderEncoder.setVertexBytes(&vertices, length: 4 * vertices.count, index: 0)
|
||||||
|
|
||||||
var resolution = simd_uint2(UInt32(viewportDimensions.width), UInt32(viewportDimensions.height))
|
var resolution = simd_uint2(UInt32(drawableSize.width), UInt32(drawableSize.height))
|
||||||
renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout<simd_uint2>.size * 2, index: 0)
|
renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout<simd_uint2>.size * 2, index: 0)
|
||||||
|
|
||||||
var primaryParameters = simd_float4(
|
var primaryParameters = simd_float4(
|
||||||
@ -340,7 +414,7 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
Float(self.primaryCornerRadius.presentationValue)
|
Float(self.primaryCornerRadius.presentationValue)
|
||||||
)
|
)
|
||||||
renderEncoder.setFragmentBytes(&primaryParameters, length: MemoryLayout<simd_float4>.size, index: 1)
|
renderEncoder.setFragmentBytes(&primaryParameters, length: MemoryLayout<simd_float4>.size, index: 1)
|
||||||
|
|
||||||
var secondaryParameters = simd_float3(
|
var secondaryParameters = simd_float3(
|
||||||
Float(self.secondarySize.presentationValue),
|
Float(self.secondarySize.presentationValue),
|
||||||
Float(self.secondaryOffset.presentationValue),
|
Float(self.secondaryOffset.presentationValue),
|
||||||
@ -350,17 +424,17 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
|||||||
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
|
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
|
||||||
renderEncoder.endEncoding()
|
renderEncoder.endEncoding()
|
||||||
|
|
||||||
commandBuffer.present(drawable)
|
|
||||||
|
var storedDrawable: MetalImageLayer.Drawable? = drawable
|
||||||
|
commandBuffer.addCompletedHandler { _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
autoreleasepool {
|
||||||
|
storedDrawable?.present(completion: {})
|
||||||
|
storedDrawable = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
|
|
||||||
self.tick()
|
|
||||||
}
|
|
||||||
|
|
||||||
func draw(in view: MTKView) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,31 @@
|
|||||||
|
|
||||||
using namespace metal;
|
using namespace metal;
|
||||||
|
|
||||||
|
static inline
|
||||||
|
float sRGB_nonLinearNormToLinear(float normV)
|
||||||
|
{
|
||||||
|
if (normV <= 0.04045f) {
|
||||||
|
normV *= (1.0f / 12.92f);
|
||||||
|
} else {
|
||||||
|
const float a = 0.055f;
|
||||||
|
const float gamma = 2.4f;
|
||||||
|
//const float gamma = 1.0f / (1.0f / 2.4f);
|
||||||
|
normV = (normV + a) * (1.0f / (1.0f + a));
|
||||||
|
normV = pow(normV, gamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normV;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
float4 sRGB_gamma_decode(const float4 rgba) {
|
||||||
|
float4 tmp = rgba;
|
||||||
|
tmp.r = sRGB_nonLinearNormToLinear(rgba.r);
|
||||||
|
tmp.g = sRGB_nonLinearNormToLinear(rgba.g);
|
||||||
|
tmp.b = sRGB_nonLinearNormToLinear(rgba.b);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
static inline float4 BT709_decode(const float Y, const float Cb, const float Cr) {
|
static inline float4 BT709_decode(const float Y, const float Cb, const float Cr) {
|
||||||
float Yn = Y;
|
float Yn = Y;
|
||||||
|
|
||||||
@ -13,8 +38,8 @@ static inline float4 BT709_decode(const float Y, const float Cb, const float Cr)
|
|||||||
float3 YCbCr = float3(Yn, Cbn, Crn);
|
float3 YCbCr = float3(Yn, Cbn, Crn);
|
||||||
|
|
||||||
const float3x3 kColorConversion709 = float3x3(float3(1.0, 1.0, 1.0),
|
const float3x3 kColorConversion709 = float3x3(float3(1.0, 1.0, 1.0),
|
||||||
float3(0.0f, -0.1873, 1.8556),
|
float3(0.0f, -0.18732, 1.8556),
|
||||||
float3(1.5748, -0.4681, 0.0));
|
float3(1.5748, -0.46812, 0.0));
|
||||||
|
|
||||||
float3 rgb = kColorConversion709 * YCbCr;
|
float3 rgb = kColorConversion709 * YCbCr;
|
||||||
|
|
||||||
@ -23,7 +48,6 @@ static inline float4 BT709_decode(const float Y, const float Cb, const float Cr)
|
|||||||
return float4(rgb.r, rgb.g, rgb.b, 1.0f);
|
return float4(rgb.r, rgb.g, rgb.b, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fragment float4 bt709ToRGBFragmentShader(RasterizerData in [[stage_in]],
|
fragment float4 bt709ToRGBFragmentShader(RasterizerData in [[stage_in]],
|
||||||
texture2d<half, access::sample> inYTexture [[texture(0)]],
|
texture2d<half, access::sample> inYTexture [[texture(0)]],
|
||||||
texture2d<half, access::sample> inUVTexture [[texture(1)]]
|
texture2d<half, access::sample> inUVTexture [[texture(1)]]
|
||||||
@ -38,5 +62,7 @@ fragment float4 bt709ToRGBFragmentShader(RasterizerData in [[stage_in]],
|
|||||||
float Cr = float(uvSamples[1]);
|
float Cr = float(uvSamples[1]);
|
||||||
|
|
||||||
float4 pixel = BT709_decode(Y, Cb, Cr);
|
float4 pixel = BT709_decode(Y, Cb, Cr);
|
||||||
|
pixel = sRGB_gamma_decode(pixel);
|
||||||
|
pixel.rgb = pow(pixel.rgb, 1.0 / 2.2);
|
||||||
return pixel;
|
return pixel;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -305,7 +305,7 @@ public final class MediaEditor {
|
|||||||
|
|
||||||
let playerItem = AVPlayerItem(asset: asset)
|
let playerItem = AVPlayerItem(asset: asset)
|
||||||
let player = AVPlayer(playerItem: playerItem)
|
let player = AVPlayer(playerItem: playerItem)
|
||||||
|
player.automaticallyWaitsToMinimizeStalling = false
|
||||||
if let transitionImage {
|
if let transitionImage {
|
||||||
let colors = mediaEditorGetGradientColors(from: transitionImage)
|
let colors = mediaEditorGetGradientColors(from: transitionImage)
|
||||||
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, colors.0, colors.1))
|
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, colors.0, colors.1))
|
||||||
@ -348,6 +348,7 @@ public final class MediaEditor {
|
|||||||
if let asset {
|
if let asset {
|
||||||
let playerItem = AVPlayerItem(asset: asset)
|
let playerItem = AVPlayerItem(asset: asset)
|
||||||
let player = AVPlayer(playerItem: playerItem)
|
let player = AVPlayer(playerItem: playerItem)
|
||||||
|
player.automaticallyWaitsToMinimizeStalling = false
|
||||||
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, colors.0, colors.1))
|
subscriber.putNext((VideoTextureSource(player: player, renderTarget: renderTarget), nil, player, colors.0, colors.1))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
@ -422,7 +423,7 @@ public final class MediaEditor {
|
|||||||
self.player?.play()
|
self.player?.play()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.player?.play()
|
player.playImmediately(atRate: 1.0)
|
||||||
self.volumeFade = self.player?.fadeVolume(from: 0.0, to: 1.0, duration: 0.4)
|
self.volumeFade = self.player?.fadeVolume(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -197,19 +197,10 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
tintColor = .white
|
tintColor = .white
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disposables.add((self.frameSource.get()
|
let processFrame: (Double, Int, (Int) -> AnimatedStickerFrame?) -> Void = { [weak self] duration, frameCount, takeFrame in
|
||||||
|> take(1)
|
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] frameSource in
|
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
completion(nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let frameSource, let duration = strongSelf.totalDuration, let frameCount = strongSelf.frameCount else {
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let relativeTime = currentTime - floor(currentTime / duration) * duration
|
let relativeTime = currentTime - floor(currentTime / duration) * duration
|
||||||
var t = relativeTime / duration
|
var t = relativeTime / duration
|
||||||
t = max(0.0, t)
|
t = max(0.0, t)
|
||||||
@ -233,12 +224,8 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
delta = max(1, frameIndex - previousFrameIndex)
|
delta = max(1, frameIndex - previousFrameIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
var frame: AnimatedStickerFrame?
|
let frame = takeFrame(delta)
|
||||||
frameSource.syncWith { frameSource in
|
|
||||||
for i in 0 ..< delta {
|
|
||||||
frame = frameSource.takeFrame(draw: i == delta - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let frame {
|
if let frame {
|
||||||
var imagePixelBuffer: CVPixelBuffer?
|
var imagePixelBuffer: CVPixelBuffer?
|
||||||
if let pixelBuffer = strongSelf.imagePixelBuffer {
|
if let pixelBuffer = strongSelf.imagePixelBuffer {
|
||||||
@ -273,7 +260,57 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
} else {
|
} else {
|
||||||
completion(strongSelf.image)
|
completion(strongSelf.image)
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
|
||||||
|
if self.isVideo {
|
||||||
|
self.disposables.add((self.videoFrameSource.get()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOn(self.queue)).start(next: { [weak self] frameSource in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let frameSource, let duration = strongSelf.totalDuration, let frameCount = strongSelf.frameCount else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processFrame(duration, frameCount, { delta in
|
||||||
|
var frame: AnimatedStickerFrame?
|
||||||
|
frameSource.syncWith { frameSource in
|
||||||
|
for i in 0 ..< delta {
|
||||||
|
frame = frameSource.takeFrame(draw: i == delta - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frame
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
self.disposables.add((self.frameSource.get()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOn(self.queue)).start(next: { [weak self] frameSource in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let frameSource, let duration = strongSelf.totalDuration, let frameCount = strongSelf.frameCount else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processFrame(duration, frameCount, { delta in
|
||||||
|
var frame: AnimatedStickerFrame?
|
||||||
|
frameSource.syncWith { frameSource in
|
||||||
|
for i in 0 ..< delta {
|
||||||
|
frame = frameSource.takeFrame(draw: i == delta - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frame
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var image: CIImage?
|
var image: CIImage?
|
||||||
if let cachedImage = self.image {
|
if let cachedImage = self.image {
|
||||||
|
|||||||
@ -221,7 +221,7 @@ final class MediaEditorRenderer: TextureConsumer {
|
|||||||
|
|
||||||
if let onNextRender = self.onNextRender {
|
if let onNextRender = self.onNextRender {
|
||||||
self.onNextRender = nil
|
self.onNextRender = nil
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().after(0.016) {
|
||||||
onNextRender()
|
onNextRender()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,10 +29,7 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
|
|||||||
private weak var player: AVPlayer?
|
private weak var player: AVPlayer?
|
||||||
private weak var playerItem: AVPlayerItem?
|
private weak var playerItem: AVPlayerItem?
|
||||||
private var playerItemOutput: AVPlayerItemVideoOutput?
|
private var playerItemOutput: AVPlayerItemVideoOutput?
|
||||||
|
|
||||||
private var playerItemStatusObservation: NSKeyValueObservation?
|
|
||||||
private var playerItemObservation: NSKeyValueObservation?
|
|
||||||
|
|
||||||
private var displayLink: CADisplayLink?
|
private var displayLink: CADisplayLink?
|
||||||
|
|
||||||
private let device: MTLDevice?
|
private let device: MTLDevice?
|
||||||
@ -57,24 +54,12 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.playerItemObservation = player.observe(\.currentItem, options: [.initial, .new], changeHandler: { [weak self] (player, change) in
|
self.updatePlayerItem(player.currentItem)
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.updatePlayerItem(player.currentItem)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.playerItemObservation?.invalidate()
|
|
||||||
self.playerItemStatusObservation?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidate() {
|
func invalidate() {
|
||||||
self.playerItemOutput?.setDelegate(nil, queue: nil)
|
self.playerItemOutput?.setDelegate(nil, queue: nil)
|
||||||
self.playerItemOutput = nil
|
self.playerItemOutput = nil
|
||||||
self.playerItemObservation?.invalidate()
|
|
||||||
self.playerItemStatusObservation?.invalidate()
|
|
||||||
self.displayLink?.invalidate()
|
self.displayLink?.invalidate()
|
||||||
self.displayLink = nil
|
self.displayLink = nil
|
||||||
}
|
}
|
||||||
@ -88,18 +73,9 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.playerItemOutput = nil
|
self.playerItemOutput = nil
|
||||||
self.playerItemStatusObservation?.invalidate()
|
|
||||||
self.playerItemStatusObservation = nil
|
|
||||||
|
|
||||||
self.playerItem = playerItem
|
self.playerItem = playerItem
|
||||||
self.playerItemStatusObservation = self.playerItem?.observe(\.status, options: [.initial, .new], changeHandler: { [weak self] item, change in
|
self.handleReadyToPlay()
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strongSelf.playerItem == item, item.status == .readyToPlay {
|
|
||||||
strongSelf.handleReadyToPlay()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleReadyToPlay() {
|
private func handleReadyToPlay() {
|
||||||
@ -273,7 +249,7 @@ final class VideoInputPass: DefaultRenderPass {
|
|||||||
textureDescriptor.textureType = .type2D
|
textureDescriptor.textureType = .type2D
|
||||||
textureDescriptor.width = outputWidth
|
textureDescriptor.width = outputWidth
|
||||||
textureDescriptor.height = outputHeight
|
textureDescriptor.height = outputHeight
|
||||||
textureDescriptor.pixelFormat = .bgra8Unorm
|
textureDescriptor.pixelFormat = self.pixelFormat
|
||||||
textureDescriptor.storageMode = .private
|
textureDescriptor.storageMode = .private
|
||||||
textureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget]
|
textureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget]
|
||||||
if let texture = device.makeTexture(descriptor: textureDescriptor) {
|
if let texture = device.makeTexture(descriptor: textureDescriptor) {
|
||||||
|
|||||||
@ -252,22 +252,22 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
var delay: Double = 0.0
|
|
||||||
for button in buttons {
|
|
||||||
if let view = button.view {
|
|
||||||
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: delay)
|
|
||||||
delay += 0.05
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let view = self.doneButton.view {
|
if let view = self.doneButton.view {
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .camera = source {
|
if case .camera = source {
|
||||||
|
var delay: Double = 0.0
|
||||||
|
for button in buttons {
|
||||||
|
if let view = button.view {
|
||||||
|
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: delay)
|
||||||
|
delay += 0.05
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let view = self.saveButton.view {
|
if let view = self.saveButton.view {
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
|
||||||
@ -702,7 +702,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
case 86400:
|
case 86400:
|
||||||
timeoutValue = "24"
|
timeoutValue = "24"
|
||||||
case 172800:
|
case 172800:
|
||||||
timeoutValue = "2d"
|
timeoutValue = "48"
|
||||||
default:
|
default:
|
||||||
timeoutValue = "24"
|
timeoutValue = "24"
|
||||||
}
|
}
|
||||||
@ -900,10 +900,10 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
saveButtonView.layer.shadowOpacity = 0.35
|
saveButtonView.layer.shadowOpacity = 0.35
|
||||||
self.addSubview(saveButtonView)
|
self.addSubview(saveButtonView)
|
||||||
}
|
}
|
||||||
|
|
||||||
let saveButtonAlpha = component.isSavingAvailable ? 1.0 : 0.3
|
let saveButtonAlpha = component.isSavingAvailable ? 1.0 : 0.3
|
||||||
saveButtonView.isUserInteractionEnabled = component.isSavingAvailable
|
saveButtonView.isUserInteractionEnabled = component.isSavingAvailable
|
||||||
|
|
||||||
transition.setPosition(view: saveButtonView, position: saveButtonFrame.center)
|
transition.setPosition(view: saveButtonView, position: saveButtonFrame.center)
|
||||||
transition.setBounds(view: saveButtonView, bounds: CGRect(origin: .zero, size: saveButtonFrame.size))
|
transition.setBounds(view: saveButtonView, bounds: CGRect(origin: .zero, size: saveButtonFrame.size))
|
||||||
transition.setScale(view: saveButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
transition.setScale(view: saveButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
||||||
@ -1640,8 +1640,10 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
self.mediaEditor?.onFirstDisplay = { [weak self] in
|
self.mediaEditor?.onFirstDisplay = { [weak self] in
|
||||||
if let self, let transitionInView = self.transitionInView {
|
if let self, let transitionInView = self.transitionInView {
|
||||||
transitionInView.removeFromSuperview()
|
|
||||||
self.transitionInView = nil
|
self.transitionInView = nil
|
||||||
|
transitionInView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionInView] _ in
|
||||||
|
transitionInView?.removeFromSuperview()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2059,6 +2061,9 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
if let self, let file {
|
if let self, let file {
|
||||||
let stickerEntity = DrawingStickerEntity(content: .file(file))
|
let stickerEntity = DrawingStickerEntity(content: .file(file))
|
||||||
self.interaction?.insertEntity(stickerEntity)
|
self.interaction?.insertEntity(stickerEntity)
|
||||||
|
|
||||||
|
self.controller?.isSavingAvailable = true
|
||||||
|
self.controller?.requestLayout(transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.controller?.present(controller, in: .current)
|
self.controller?.present(controller, in: .current)
|
||||||
@ -2066,6 +2071,9 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
case .text:
|
case .text:
|
||||||
let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white))
|
let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, animation: .none, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white))
|
||||||
self.interaction?.insertEntity(textEntity)
|
self.interaction?.insertEntity(textEntity)
|
||||||
|
|
||||||
|
self.controller?.isSavingAvailable = true
|
||||||
|
self.controller?.requestLayout(transition: .immediate)
|
||||||
return
|
return
|
||||||
case .drawing:
|
case .drawing:
|
||||||
self.interaction?.deactivate()
|
self.interaction?.deactivate()
|
||||||
@ -2496,7 +2504,7 @@ public final class MediaEditorScreen: ViewController {
|
|||||||
|
|
||||||
updateTimeout(86400, false)
|
updateTimeout(86400, false)
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "2 Days", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in
|
||||||
return currentValue == 86400 * 2 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
return currentValue == 86400 * 2 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|||||||
@ -132,7 +132,7 @@ final class VideoScrubberComponent: Component {
|
|||||||
private var isPanningPositionHandle = false
|
private var isPanningPositionHandle = false
|
||||||
|
|
||||||
private var displayLink: SharedDisplayLinkDriver.Link?
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
||||||
private var positionAnimation: (start: Double, from: Double, to: Double)?
|
private var positionAnimation: (start: Double, from: Double, to: Double, ended: Bool)?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -343,12 +343,12 @@ final class VideoScrubberComponent: Component {
|
|||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
let updatedPosition: Double
|
let updatedPosition: Double
|
||||||
if let (start, from, to) = self.positionAnimation {
|
if let (start, from, to, _) = self.positionAnimation {
|
||||||
let duration = to - from
|
let duration = to - from
|
||||||
let fraction = duration > 0.0 ? (timestamp - start) / duration : 0.0
|
let fraction = duration > 0.0 ? (timestamp - start) / duration : 0.0
|
||||||
updatedPosition = max(component.startPosition, min(component.endPosition, from + (to - from) * fraction))
|
updatedPosition = max(component.startPosition, min(component.endPosition, from + (to - from) * fraction))
|
||||||
if fraction >= 1.0 {
|
if fraction >= 1.0 {
|
||||||
self.positionAnimation = (timestamp, component.startPosition, component.endPosition)
|
self.positionAnimation = (start, from, to, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let advance = component.isPlaying ? timestamp - component.generationTimestamp : 0.0
|
let advance = component.isPlaying ? timestamp - component.generationTimestamp : 0.0
|
||||||
@ -419,8 +419,12 @@ final class VideoScrubberComponent: Component {
|
|||||||
self.displayLink?.isPaused = true
|
self.displayLink?.isPaused = true
|
||||||
transition.setFrame(view: self.cursorView, frame: cursorFrame(size: scrubberSize, position: component.position, duration: component.duration))
|
transition.setFrame(view: self.cursorView, frame: cursorFrame(size: scrubberSize, position: component.position, duration: component.duration))
|
||||||
} else {
|
} else {
|
||||||
if self.positionAnimation == nil {
|
if let (_, _, end, ended) = self.positionAnimation {
|
||||||
self.positionAnimation = (CACurrentMediaTime(), component.position, component.endPosition)
|
if ended, component.position >= component.startPosition && component.position < end - 1.0 {
|
||||||
|
self.positionAnimation = (CACurrentMediaTime(), component.position, component.endPosition, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.positionAnimation = (CACurrentMediaTime(), component.position, component.endPosition, false)
|
||||||
}
|
}
|
||||||
self.displayLink?.isPaused = false
|
self.displayLink?.isPaused = false
|
||||||
self.updateCursorPosition()
|
self.updateCursorPosition()
|
||||||
|
|||||||
@ -113,7 +113,7 @@ public enum NotificationExceptionMode : Equatable {
|
|||||||
case .default:
|
case .default:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .default, messageSound: sound, displayPreviews: .default), peer: peer, date: Date().timeIntervalSince1970)
|
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .default, messageSound: sound, displayPreviews: .default, storiesMuted: nil), peer: peer, date: Date().timeIntervalSince1970)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
@ -149,7 +149,7 @@ public enum NotificationExceptionMode : Equatable {
|
|||||||
case .default:
|
case .default:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: muteState, messageSound: .default, displayPreviews: .default), peer: peer, date: Date().timeIntervalSince1970)
|
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: muteState, messageSound: .default, displayPreviews: .default, storiesMuted: nil), peer: peer, date: Date().timeIntervalSince1970)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
@ -201,7 +201,7 @@ public enum NotificationExceptionMode : Equatable {
|
|||||||
case .default:
|
case .default:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: displayPreviews), peer: peer, date: Date().timeIntervalSince1970)
|
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: displayPreviews, storiesMuted: nil), peer: peer, date: Date().timeIntervalSince1970)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user