mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-19 09:41:29 +00:00
Various improvements
This commit is contained in:
parent
2f440b5453
commit
0a56b5bfa7
@ -14462,3 +14462,37 @@ Sorry for the inconvenience.";
|
|||||||
"SuggestPost.Time.SendToday" = "Post today at %@";
|
"SuggestPost.Time.SendToday" = "Post today at %@";
|
||||||
"SuggestPost.Time.SendTomorrow" = "Post tomorrow at %@";
|
"SuggestPost.Time.SendTomorrow" = "Post tomorrow at %@";
|
||||||
"SuggestPost.Time.SendOn" = "Post on %@ at %@";
|
"SuggestPost.Time.SendOn" = "Post on %@ at %@";
|
||||||
|
|
||||||
|
"Attachment.Todo" = "To-Do List";
|
||||||
|
|
||||||
|
"Premium.Todo" = "To-Do Lists";
|
||||||
|
"Premium.TodoInfo" = "Plan, assign and complete tasks – seamlessly and efficiently.";
|
||||||
|
|
||||||
|
"CreateTodo.Title" = "Title";
|
||||||
|
"CreateTodo.TodoTitle" = "TO-DO LIST";
|
||||||
|
"CreateTodo.TitlePlaceholder" = "Title";
|
||||||
|
"CreateTodo.TaskPlaceholder" = "Task";
|
||||||
|
"CreateTodo.AddTaskPlaceholder" = "Add a Task";
|
||||||
|
"CreateTodo.TaskCountFooterFormat_1" = "You can add {count} more task.";
|
||||||
|
"CreateTodo.TaskCountFooterFormat_any" = "You can add {count} more tasks.";
|
||||||
|
"CreateTodo.TaskCountLimitReached" = "Maximum number of tasks reached.";
|
||||||
|
|
||||||
|
"CreateTodo.AllowOthersToComplete" = "Allow Others to Mark as Done";
|
||||||
|
"CreateTodo.AllowOthersToAppend" = "Allow Others to Add Tasks";
|
||||||
|
|
||||||
|
"Chat.Todo.Message.Title" = "To-do List";
|
||||||
|
"Chat.Todo.Message.TitleGroup" = "Group To-do List";
|
||||||
|
"Chat.Todo.Message.TitlePersonal" = "%@'s To-do List";
|
||||||
|
"Chat.Todo.Message.CompletedPersonal" = "%@'s To-do List";
|
||||||
|
|
||||||
|
"Chat.Todo.ContextMenu.AddTask" = "Add a Task";
|
||||||
|
"Chat.Todo.ContextMenu.EditTask" = "Edit Item";
|
||||||
|
"Chat.Todo.ContextMenu.DeleteTask" = "Delete Item";
|
||||||
|
"Chat.Todo.ContextMenu.CheckTask" = "Check";
|
||||||
|
"Chat.Todo.ContextMenu.UncheckTask" = "Uncheck";
|
||||||
|
"Chat.Todo.ContextMenu.SectionTask" = "Task";
|
||||||
|
"Chat.Todo.ContextMenu.SectionList" = "List";
|
||||||
|
"Chat.Todo.ContextMenu.SectionsInfo" = "You're viewing actions for one task.\nYou can switch to actions for the list.";
|
||||||
|
|
||||||
|
"Chat.Todo.PremiumRequired" = "Only [Telegram Premium]() subscribers can mark tasks as done.";
|
||||||
|
"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this to do list.";
|
||||||
|
@ -196,7 +196,7 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
name = strings.Attachment_Location
|
name = strings.Attachment_Location
|
||||||
imageName = "Chat/Attach Menu/Location"
|
imageName = "Chat/Attach Menu/Location"
|
||||||
case .todo:
|
case .todo:
|
||||||
name = "To Do List"
|
name = strings.Attachment_Todo
|
||||||
imageName = "Chat/Attach Menu/Todo"
|
imageName = "Chat/Attach Menu/Todo"
|
||||||
case .contact:
|
case .contact:
|
||||||
name = strings.Attachment_Contact
|
name = strings.Attachment_Contact
|
||||||
@ -1497,8 +1497,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
case .location:
|
case .location:
|
||||||
accessibilityTitle = self.presentationData.strings.Attachment_Location
|
accessibilityTitle = self.presentationData.strings.Attachment_Location
|
||||||
case .todo:
|
case .todo:
|
||||||
//TODO:localize
|
accessibilityTitle = self.presentationData.strings.Attachment_Todo
|
||||||
accessibilityTitle = "To Do List"
|
|
||||||
case .contact:
|
case .contact:
|
||||||
accessibilityTitle = self.presentationData.strings.Attachment_Contact
|
accessibilityTitle = self.presentationData.strings.Attachment_Contact
|
||||||
case .poll:
|
case .poll:
|
||||||
|
@ -233,7 +233,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||||||
private var adState: (startDelay: Int32?, betweenDelay: Int32?, messages: [Message])?
|
private var adState: (startDelay: Int32?, betweenDelay: Int32?, messages: [Message])?
|
||||||
private let adDisposable = MetaDisposable()
|
private let adDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var program: [(Int32, Message?)] = []
|
private var adSchedule: [(Int32, Message?)] = []
|
||||||
|
|
||||||
var performAction: ((GalleryControllerInteractionTapAction) -> Void)?
|
var performAction: ((GalleryControllerInteractionTapAction) -> Void)?
|
||||||
var presentPremiumDemo: (() -> Void)?
|
var presentPremiumDemo: (() -> Void)?
|
||||||
@ -263,26 +263,26 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||||||
self.adState = (state.startDelay, state.betweenDelay, state.messages)
|
self.adState = (state.startDelay, state.betweenDelay, state.messages)
|
||||||
|
|
||||||
var startTime = Int32(CFAbsoluteTimeGetCurrent()) + (state.startDelay ?? 0)
|
var startTime = Int32(CFAbsoluteTimeGetCurrent()) + (state.startDelay ?? 0)
|
||||||
var program: [(Int32, Message?)] = []
|
var schedule: [(Int32, Message?)] = []
|
||||||
var maxDisplayDuration: Int32 = 30
|
var maxDisplayDuration: Int32 = 30
|
||||||
for message in state.messages {
|
for message in state.messages {
|
||||||
if !program.isEmpty {
|
if !schedule.isEmpty {
|
||||||
program.append((startTime, nil))
|
schedule.append((startTime, nil))
|
||||||
startTime += (state.betweenDelay ?? 0)
|
startTime += (state.betweenDelay ?? 0)
|
||||||
}
|
}
|
||||||
program.append((startTime, message))
|
schedule.append((startTime, message))
|
||||||
|
|
||||||
if let adAttribute = message.adAttribute {
|
if let adAttribute = message.adAttribute {
|
||||||
maxDisplayDuration = adAttribute.maxDisplayDuration ?? 30
|
maxDisplayDuration = adAttribute.maxDisplayDuration ?? 30
|
||||||
startTime += maxDisplayDuration
|
startTime += maxDisplayDuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
program.append((startTime + maxDisplayDuration, nil))
|
schedule.append((startTime + maxDisplayDuration, nil))
|
||||||
self.program = program
|
self.adSchedule = schedule
|
||||||
} else {
|
} else {
|
||||||
self.adState = nil
|
self.adState = nil
|
||||||
|
|
||||||
self.program = []
|
self.adSchedule = []
|
||||||
}
|
}
|
||||||
|
|
||||||
if let validLayout = self.validLayout {
|
if let validLayout = self.validLayout {
|
||||||
@ -317,7 +317,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent())
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent())
|
||||||
var currentAd: (Int32, Message?)?
|
var currentAd: (Int32, Message?)?
|
||||||
|
|
||||||
for (time, maybeMessage) in program {
|
for (time, maybeMessage) in adSchedule {
|
||||||
if currentTime > time {
|
if currentTime > time {
|
||||||
currentAd = (time, maybeMessage)
|
currentAd = (time, maybeMessage)
|
||||||
}
|
}
|
||||||
|
@ -2718,7 +2718,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
if price == nil {
|
if price == nil {
|
||||||
//TODO:localize
|
|
||||||
items.append(.action(ContextMenuActionItem(text: strings.Attachment_SendInHd, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strings.Attachment_SendInHd, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QualityHd"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QualityHd"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 332 B |
@ -372,6 +372,7 @@ final class PhoneDemoComponent: Component {
|
|||||||
case hello
|
case hello
|
||||||
case tag
|
case tag
|
||||||
case business
|
case business
|
||||||
|
case todo
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Model {
|
enum Model {
|
||||||
@ -489,29 +490,29 @@ final class PhoneDemoComponent: Component {
|
|||||||
case .dataRain:
|
case .dataRain:
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
if let _ = self.decorationView as? MatrixView {
|
if let _ = self.decorationView as? MatrixView {
|
||||||
} else if let rainView = MatrixView(test: true) {
|
} else if let decorationView = MatrixView(test: true) {
|
||||||
rainView.frame = self.decorationContainerView.bounds.insetBy(dx: availableSize.width * 0.5, dy: 0.0)
|
decorationView.frame = self.decorationContainerView.bounds.insetBy(dx: availableSize.width * 0.5, dy: 0.0)
|
||||||
self.decorationView = rainView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(rainView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .swirlStars:
|
case .swirlStars:
|
||||||
if let _ = self.decorationView as? SwirlStarsView {
|
if let _ = self.decorationView as? SwirlStarsView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = SwirlStarsView(frame: self.decorationContainerView.bounds)
|
let decorationView = SwirlStarsView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
case .fasterStars:
|
case .fasterStars:
|
||||||
if let _ = self.decorationView as? FasterStarsView {
|
if let _ = self.decorationView as? FasterStarsView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = FasterStarsView(frame: self.decorationContainerView.bounds)
|
let decorationView = FasterStarsView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
|
|
||||||
self.playbackStatusDisposable = (self.phoneView.playbackStatus
|
self.playbackStatusDisposable = (self.phoneView.playbackStatus
|
||||||
|> deliverOnMainQueue).start(next: { [weak starsView] status in
|
|> deliverOnMainQueue).start(next: { [weak decorationView] status in
|
||||||
if let starsView = starsView, let status = status {
|
if let starsView = decorationView, let status = status {
|
||||||
if status.timestamp > 8.0 {
|
if status.timestamp > 8.0 {
|
||||||
starsView.resetAnimation()
|
starsView.resetAnimation()
|
||||||
} else if status.timestamp > 0.85 {
|
} else if status.timestamp > 0.85 {
|
||||||
@ -523,37 +524,44 @@ final class PhoneDemoComponent: Component {
|
|||||||
case .badgeStars:
|
case .badgeStars:
|
||||||
if let _ = self.decorationView as? BadgeStarsView {
|
if let _ = self.decorationView as? BadgeStarsView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = BadgeStarsView(frame: self.decorationContainerView.bounds)
|
let decorationView = BadgeStarsView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
case .emoji:
|
case .emoji:
|
||||||
if let _ = self.decorationView as? EmojiStarsView {
|
if let _ = self.decorationView as? EmojiStarsView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = EmojiStarsView(frame: self.decorationContainerView.bounds)
|
let decorationView = EmojiStarsView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
case .hello:
|
case .hello:
|
||||||
if let _ = self.decorationView as? HelloView {
|
if let _ = self.decorationView as? HelloView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = HelloView(frame: self.decorationContainerView.bounds)
|
let decorationView = HelloView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
case .tag:
|
case .tag:
|
||||||
if let _ = self.decorationView as? TagStarsView {
|
if let _ = self.decorationView as? TagStarsView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = TagStarsView(frame: self.decorationContainerView.bounds)
|
let decorationView = TagStarsView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
case .business:
|
case .business:
|
||||||
if let _ = self.decorationView as? BadgeBusinessView {
|
if let _ = self.decorationView as? BadgeBusinessView {
|
||||||
} else {
|
} else {
|
||||||
let starsView = BadgeBusinessView(frame: self.decorationContainerView.bounds)
|
let decorationView = BadgeBusinessView(frame: self.decorationContainerView.bounds)
|
||||||
self.decorationView = starsView
|
self.decorationView = decorationView
|
||||||
self.decorationContainerView.addSubview(starsView)
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
|
}
|
||||||
|
case .todo:
|
||||||
|
if let _ = self.decorationView as? TodoChecksView {
|
||||||
|
} else {
|
||||||
|
let decorationView = TodoChecksView(frame: self.decorationContainerView.bounds)
|
||||||
|
self.decorationView = decorationView
|
||||||
|
self.decorationContainerView.addSubview(decorationView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1118,7 +1118,6 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
availableItems[.todo] = DemoPagerComponent.Item(
|
availableItems[.todo] = DemoPagerComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
id: PremiumDemoScreen.Subject.todo,
|
id: PremiumDemoScreen.Subject.todo,
|
||||||
@ -1128,10 +1127,10 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
position: .top,
|
position: .top,
|
||||||
videoFile: configuration.videos["todo"],
|
videoFile: configuration.videos["todo"],
|
||||||
decoration: .badgeStars
|
decoration: .todo
|
||||||
)),
|
)),
|
||||||
title: "To-Do Lists",
|
title: strings.Premium_Todo,
|
||||||
text: "Plan, assign and complete tasks – seamlessly and efficiently.",
|
text: strings.Premium_TodoInfo,
|
||||||
textColor: textColor
|
textColor: textColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1237,8 +1236,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
text = strings.Premium_PaidMessagesInfo
|
text = strings.Premium_PaidMessagesInfo
|
||||||
case .todo:
|
case .todo:
|
||||||
//TODO:localize
|
text = strings.Premium_TodoInfo
|
||||||
text = "Plan, assign and complete tasks – seamlessly and efficiently."
|
|
||||||
default:
|
default:
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
|
@ -685,8 +685,7 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return strings.Premium_PaidMessages
|
return strings.Premium_PaidMessages
|
||||||
case .todo:
|
case .todo:
|
||||||
//TODO:localize
|
return strings.Premium_Todo
|
||||||
return "To-Do Lists"
|
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
return strings.Business_Location
|
return strings.Business_Location
|
||||||
case .businessHours:
|
case .businessHours:
|
||||||
@ -757,8 +756,7 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return strings.Premium_PaidMessagesInfo
|
return strings.Premium_PaidMessagesInfo
|
||||||
case .todo:
|
case .todo:
|
||||||
//TODO:localize
|
return strings.Premium_TodoInfo
|
||||||
return "Plan, assign and complete tasks – seamlessly and efficiently."
|
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
return strings.Business_LocationInfo
|
return strings.Business_LocationInfo
|
||||||
case .businessHours:
|
case .businessHours:
|
||||||
|
@ -861,6 +861,26 @@ public class PremiumLimitsListScreen: ViewController {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
availableItems[.todo] = DemoPagerComponent.Item(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: PremiumDemoScreen.Subject.todo,
|
||||||
|
component: AnyComponent(
|
||||||
|
PageComponent(
|
||||||
|
content: AnyComponent(PhoneDemoComponent(
|
||||||
|
context: context,
|
||||||
|
position: .top,
|
||||||
|
videoFile: videos["todo"],
|
||||||
|
decoration: .todo
|
||||||
|
)),
|
||||||
|
title: strings.Premium_Todo,
|
||||||
|
text: strings.Premium_TodoInfo,
|
||||||
|
textColor: textColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
availableItems[.business] = DemoPagerComponent.Item(
|
availableItems[.business] = DemoPagerComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
id: PremiumDemoScreen.Subject.business,
|
id: PremiumDemoScreen.Subject.business,
|
||||||
|
248
submodules/PremiumUI/Sources/TodoChecksView.swift
Normal file
248
submodules/PremiumUI/Sources/TodoChecksView.swift
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||||
|
private struct Particle {
|
||||||
|
var trackIndex: Int
|
||||||
|
var position: CGPoint
|
||||||
|
var scale: CGFloat
|
||||||
|
var alpha: CGFloat
|
||||||
|
var direction: CGPoint
|
||||||
|
var velocity: CGFloat
|
||||||
|
var color: UIColor
|
||||||
|
var currentTime: CGFloat
|
||||||
|
var lifeTime: CGFloat
|
||||||
|
|
||||||
|
init(
|
||||||
|
trackIndex: Int,
|
||||||
|
position: CGPoint,
|
||||||
|
scale: CGFloat,
|
||||||
|
alpha: CGFloat,
|
||||||
|
direction: CGPoint,
|
||||||
|
velocity: CGFloat,
|
||||||
|
color: UIColor,
|
||||||
|
currentTime: CGFloat,
|
||||||
|
lifeTime: CGFloat
|
||||||
|
) {
|
||||||
|
self.trackIndex = trackIndex
|
||||||
|
self.position = position
|
||||||
|
self.scale = scale
|
||||||
|
self.alpha = alpha
|
||||||
|
self.direction = direction
|
||||||
|
self.velocity = velocity
|
||||||
|
self.color = color
|
||||||
|
self.currentTime = currentTime
|
||||||
|
self.lifeTime = lifeTime
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func update(deltaTime: CGFloat) {
|
||||||
|
var position = self.position
|
||||||
|
position.x += self.direction.x * self.velocity * deltaTime
|
||||||
|
position.y += self.direction.y * self.velocity * deltaTime
|
||||||
|
self.position = position
|
||||||
|
self.currentTime += deltaTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ParticleSet {
|
||||||
|
private let size: CGSize
|
||||||
|
private(set) var particles: [Particle] = []
|
||||||
|
|
||||||
|
init(size: CGSize, preAdvance: Bool) {
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
self.generateParticles(preAdvance: preAdvance)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateParticles(preAdvance: Bool) {
|
||||||
|
let maxDirections = 16
|
||||||
|
|
||||||
|
if self.particles.count < maxDirections {
|
||||||
|
var allTrackIndices: [Int] = Array(repeating: 0, count: maxDirections)
|
||||||
|
for i in 0 ..< maxDirections {
|
||||||
|
allTrackIndices[i] = i
|
||||||
|
}
|
||||||
|
var takenIndexCount = 0
|
||||||
|
for particle in self.particles {
|
||||||
|
allTrackIndices[particle.trackIndex] = -1
|
||||||
|
takenIndexCount += 1
|
||||||
|
}
|
||||||
|
var availableTrackIndices: [Int] = []
|
||||||
|
availableTrackIndices.reserveCapacity(maxDirections - takenIndexCount)
|
||||||
|
for index in allTrackIndices {
|
||||||
|
if index != -1 {
|
||||||
|
availableTrackIndices.append(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !availableTrackIndices.isEmpty {
|
||||||
|
availableTrackIndices.shuffle()
|
||||||
|
|
||||||
|
for takeIndex in availableTrackIndices {
|
||||||
|
let directionIndex = takeIndex
|
||||||
|
var angle = (CGFloat(directionIndex % maxDirections) / CGFloat(maxDirections)) * CGFloat.pi * 2.0
|
||||||
|
var lifeTimeMultiplier = 1.0
|
||||||
|
|
||||||
|
var isUpOrDownSemisphere = false
|
||||||
|
if angle > CGFloat.pi / 7.0 && angle < CGFloat.pi - CGFloat.pi / 7.0 {
|
||||||
|
isUpOrDownSemisphere = true
|
||||||
|
} else if !"".isEmpty, angle > CGFloat.pi + CGFloat.pi / 7.0 && angle < 2.0 * CGFloat.pi - CGFloat.pi / 7.0 {
|
||||||
|
isUpOrDownSemisphere = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isUpOrDownSemisphere {
|
||||||
|
if CGFloat.random(in: 0.0 ... 1.0) < 0.2 {
|
||||||
|
lifeTimeMultiplier = 0.3
|
||||||
|
} else {
|
||||||
|
angle += CGFloat.random(in: 0.0 ... 1.0) > 0.5 ? CGFloat.pi / 1.6 : -CGFloat.pi / 1.6
|
||||||
|
angle += CGFloat.random(in: -0.2 ... 0.2)
|
||||||
|
lifeTimeMultiplier = 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if self.large {
|
||||||
|
// angle += CGFloat.random(in: -0.5 ... 0.5)
|
||||||
|
// }
|
||||||
|
|
||||||
|
let direction = CGPoint(x: cos(angle), y: sin(angle))
|
||||||
|
let velocity = CGFloat.random(in: 15.0 ..< 20.0)
|
||||||
|
let scale = 1.0
|
||||||
|
let lifeTime = CGFloat.random(in: 2.0 ... 3.5)
|
||||||
|
|
||||||
|
var position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||||
|
var initialOffset: CGFloat = 0.5
|
||||||
|
if preAdvance {
|
||||||
|
initialOffset = CGFloat.random(in: 0.5 ... 1.0)
|
||||||
|
} else {
|
||||||
|
let p = CGFloat.random(in: 0.0 ... 1.0)
|
||||||
|
if p < 0.5 {
|
||||||
|
initialOffset = CGFloat.random(in: 0.65 ... 1.0)
|
||||||
|
} else {
|
||||||
|
initialOffset = 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position.x += direction.x * initialOffset * 225.0
|
||||||
|
position.y += direction.y * initialOffset * 225.0
|
||||||
|
|
||||||
|
let particle = Particle(
|
||||||
|
trackIndex: directionIndex,
|
||||||
|
position: position,
|
||||||
|
scale: scale,
|
||||||
|
alpha: 1.0,
|
||||||
|
direction: direction,
|
||||||
|
velocity: velocity,
|
||||||
|
color: .white,
|
||||||
|
currentTime: 0.0,
|
||||||
|
lifeTime: lifeTime * lifeTimeMultiplier
|
||||||
|
)
|
||||||
|
self.particles.append(particle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(deltaTime: CGFloat) {
|
||||||
|
for i in (0 ..< self.particles.count).reversed() {
|
||||||
|
self.particles[i].update(deltaTime: deltaTime)
|
||||||
|
if self.particles[i].currentTime > self.particles[i].lifeTime {
|
||||||
|
self.particles.remove(at: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.generateParticles(preAdvance: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
||||||
|
|
||||||
|
private var particleSet: ParticleSet?
|
||||||
|
private let particleImage: UIImage
|
||||||
|
private var particleLayers: [SimpleLayer] = []
|
||||||
|
|
||||||
|
private var size: CGSize?
|
||||||
|
private let large: Bool = false
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
// if large {
|
||||||
|
// self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), color: .white)!.withRenderingMode(.alwaysTemplate)
|
||||||
|
// } else {
|
||||||
|
self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/Particle"), color: .white)!.withRenderingMode(.alwaysTemplate)
|
||||||
|
// }
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.particleSet = ParticleSet(size: frame.size, preAdvance: true)
|
||||||
|
|
||||||
|
self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] delta in
|
||||||
|
self?.update(deltaTime: CGFloat(delta))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func update(size: CGSize) {
|
||||||
|
self.size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
private func update(deltaTime: CGFloat) {
|
||||||
|
guard let particleSet = self.particleSet else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
particleSet.update(deltaTime: deltaTime)
|
||||||
|
|
||||||
|
for i in 0 ..< particleSet.particles.count {
|
||||||
|
let particle = particleSet.particles[i]
|
||||||
|
|
||||||
|
let particleLayer: SimpleLayer
|
||||||
|
if i < self.particleLayers.count {
|
||||||
|
particleLayer = self.particleLayers[i]
|
||||||
|
particleLayer.isHidden = false
|
||||||
|
} else {
|
||||||
|
particleLayer = SimpleLayer()
|
||||||
|
particleLayer.contents = self.particleImage.cgImage
|
||||||
|
particleLayer.bounds = CGRect(origin: CGPoint(), size: self.particleImage.size)
|
||||||
|
self.particleLayers.append(particleLayer)
|
||||||
|
self.layer.addSublayer(particleLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
particleLayer.layerTintColor = particle.color.cgColor
|
||||||
|
|
||||||
|
particleLayer.position = particle.position
|
||||||
|
particleLayer.opacity = Float(particle.alpha)
|
||||||
|
|
||||||
|
let particleScale = min(1.0, particle.currentTime / 0.3) * min(1.0, (particle.lifeTime - particle.currentTime) / 0.2) * particle.scale
|
||||||
|
particleLayer.transform = CATransform3DMakeScale(particleScale, particleScale, 1.0)
|
||||||
|
}
|
||||||
|
if particleSet.particles.count < self.particleLayers.count {
|
||||||
|
for i in particleSet.particles.count ..< self.particleLayers.count {
|
||||||
|
self.particleLayers[i].isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var visible = false
|
||||||
|
func setVisible(_ visible: Bool) {
|
||||||
|
guard self.visible != visible else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.visible = visible
|
||||||
|
|
||||||
|
self.displayLink?.isPaused = !visible
|
||||||
|
|
||||||
|
// let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
|
||||||
|
// transition.updateAlpha(layer: self.containerView.layer, alpha: visible ? 1.0 : 0.0, completion: { [weak self] finished in
|
||||||
|
// if let strongSelf = self, finished && !visible && !strongSelf.visible {
|
||||||
|
// for view in strongSelf.containerView.subviews {
|
||||||
|
// view.removeFromSuperview()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetAnimation() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -6618,6 +6618,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getTodoTaskRect(id: Int32) -> CGRect? {
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
if let contentNode = contentNode as? ChatMessageTodoBubbleContentNode {
|
||||||
|
if let result = contentNode.getTaskRect(id: id) {
|
||||||
|
return contentNode.view.convert(result, to: self.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
public func hasExpandedAudioTranscription() -> Bool {
|
public func hasExpandedAudioTranscription() -> Bool {
|
||||||
for contentNode in self.contentNodes {
|
for contentNode in self.contentNodes {
|
||||||
if let contentNode = contentNode as? ChatMessageFileBubbleContentNode {
|
if let contentNode = contentNode as? ChatMessageFileBubbleContentNode {
|
||||||
|
@ -446,7 +446,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
|||||||
strongSelf.previousOptionNode?.separatorNode.layer.removeAnimation(forKey: "opacity")
|
strongSelf.previousOptionNode?.separatorNode.layer.removeAnimation(forKey: "opacity")
|
||||||
strongSelf.previousOptionNode?.separatorNode.alpha = 0.0
|
strongSelf.previousOptionNode?.separatorNode.alpha = 0.0
|
||||||
|
|
||||||
Queue.mainQueue().after(0.8) {
|
Queue.mainQueue().after(0.5) {
|
||||||
if strongSelf.highlightedBackgroundNode.alpha == 1.0 {
|
if strongSelf.highlightedBackgroundNode.alpha == 1.0 {
|
||||||
strongSelf.ignoreNextTap = true
|
strongSelf.ignoreNextTap = true
|
||||||
strongSelf.longTapped?()
|
strongSelf.longTapped?()
|
||||||
@ -986,20 +986,20 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
let typeText: String
|
let typeText: String
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
if let todo, todo.flags.contains(.othersCanComplete) {
|
if let todo, todo.flags.contains(.othersCanComplete) {
|
||||||
typeText = "Group To Do List"
|
typeText = item.presentationData.strings.Chat_Todo_Message_TitleGroup
|
||||||
} else {
|
} else {
|
||||||
if let author = item.message.author, author.id != item.context.account.peerId {
|
if let author = item.message.author, author.id != item.context.account.peerId {
|
||||||
typeText = "\(EnginePeer(author).compactDisplayTitle)'s To Do List"
|
typeText = item.presentationData.strings.Chat_Todo_Message_TitlePersonal(EnginePeer(author).compactDisplayTitle).string
|
||||||
} else {
|
} else {
|
||||||
typeText = "To Do List"
|
typeText = item.presentationData.strings.Chat_Todo_Message_Title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
var bottomText: String = ""
|
var bottomText: String = ""
|
||||||
if let todo {
|
if let todo {
|
||||||
if let author = item.message.author, author.id != item.context.account.peerId && !todo.flags.contains(.othersCanComplete) {
|
if let author = item.message.author, author.id != item.context.account.peerId && !todo.flags.contains(.othersCanComplete) {
|
||||||
@ -1149,7 +1149,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
guard let strongSelf = self, let item = strongSelf.item, let todoItem, let optionNode, let contentNode = strongSelf.contextContentNodeForItem(itemNode: optionNode) else {
|
guard let strongSelf = self, let item = strongSelf.item, let todoItem, let optionNode, let contentNode = strongSelf.contextContentNodeForItem(itemNode: optionNode) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.controllerInteraction.todoItemLongTap(todoItem.id, ChatControllerInteraction.LongTapParams(message: message, contentNode: contentNode, messageNode: strongSelf, progress: nil))
|
item.controllerInteraction.todoItemLongTap(todoItem.id, ChatControllerInteraction.LongTapParams(message: item.message, contentNode: contentNode, messageNode: strongSelf, progress: nil))
|
||||||
}
|
}
|
||||||
optionNode.frame = optionNodeFrame
|
optionNode.frame = optionNodeFrame
|
||||||
} else {
|
} else {
|
||||||
@ -1310,6 +1310,28 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getTaskRect(id: Int32?) -> CGRect? {
|
||||||
|
var rectsSet: [CGRect] = []
|
||||||
|
for node in self.optionNodes {
|
||||||
|
if node.option?.id == id {
|
||||||
|
rectsSet.append(node.frame.insetBy(dx: 3.0 - UIScreenPixel, dy: 2.0 - UIScreenPixel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !rectsSet.isEmpty {
|
||||||
|
var currentRect = CGRect()
|
||||||
|
for rect in rectsSet {
|
||||||
|
if currentRect.isEmpty {
|
||||||
|
currentRect = rect
|
||||||
|
} else {
|
||||||
|
currentRect = currentRect.union(rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentRect.offsetBy(dx: self.textNode.textNode.frame.minX, dy: self.textNode.textNode.frame.minY)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
private var taskHighlightingNode: LinkHighlightingNode?
|
private var taskHighlightingNode: LinkHighlightingNode?
|
||||||
public func updateTaskHighlightState(id: Int32?, color: UIColor, animated: Bool) {
|
public func updateTaskHighlightState(id: Int32?, color: UIColor, animated: Bool) {
|
||||||
var rectsSet: [CGRect] = []
|
var rectsSet: [CGRect] = []
|
||||||
|
@ -833,7 +833,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
transition.setFrame(view: todoTextSectionView, frame: todoTextSectionFrame)
|
transition.setFrame(view: todoTextSectionView, frame: todoTextSectionFrame)
|
||||||
|
|
||||||
if let itemView = todoTextSectionView.itemView(id: 0) as? ListComposePollOptionComponent.View {
|
if let itemView = todoTextSectionView.itemView(id: 0) as? ListComposePollOptionComponent.View {
|
||||||
itemView.updateCustomPlaceholder(value: "Title", size: itemView.bounds.size, transition: .immediate)
|
itemView.updateCustomPlaceholder(value: environment.strings.CreateTodo_TitlePlaceholder, size: itemView.bounds.size, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentHeight += todoTextSectionSize.height
|
contentHeight += todoTextSectionSize.height
|
||||||
@ -991,12 +991,12 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
var activate = false
|
var activate = false
|
||||||
let placeholder: String
|
let placeholder: String
|
||||||
if i == todoItemsSectionReadyItems.count - 1 {
|
if i == todoItemsSectionReadyItems.count - 1 {
|
||||||
placeholder = "Add a Task"
|
placeholder = environment.strings.CreateTodo_AddTaskPlaceholder
|
||||||
if isFirstTime, component.initialData.append {
|
if isFirstTime, component.initialData.append {
|
||||||
activate = true
|
activate = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
placeholder = "Task"
|
placeholder = environment.strings.CreateTodo_TaskPlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
if let focusedIndex, i == focusedIndex {
|
if let focusedIndex, i == focusedIndex {
|
||||||
@ -1030,7 +1030,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: "TO DO LIST",
|
string: environment.strings.CreateTodo_TodoTitle,
|
||||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||||
textColor: theme.list.freeTextColor
|
textColor: theme.list.freeTextColor
|
||||||
)),
|
)),
|
||||||
@ -1086,7 +1086,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
let textColor = theme.list.freeTextColor
|
let textColor = theme.list.freeTextColor
|
||||||
todoItemsComponent = AnyComponent(MultilineTextComponent(
|
todoItemsComponent = AnyComponent(MultilineTextComponent(
|
||||||
text: .markdown(
|
text: .markdown(
|
||||||
text: "Maximum number of tasks reached.",
|
text: environment.strings.CreateTodo_TaskCountLimitReached,
|
||||||
attributes: MarkdownAttributes(
|
attributes: MarkdownAttributes(
|
||||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||||
@ -1120,7 +1120,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let remainingCount = component.initialData.maxTodoItemsCount - self.todoItems.count
|
let remainingCount = component.initialData.maxTodoItemsCount - self.todoItems.count
|
||||||
let rawString = "You can add {count} more tasks." //environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
|
let rawString = environment.strings.CreateTodo_TaskCountFooterFormat(Int32(remainingCount))
|
||||||
|
|
||||||
var todoItemsFooterItems: [AnimatedTextComponent.Item] = []
|
var todoItemsFooterItems: [AnimatedTextComponent.Item] = []
|
||||||
if let range = rawString.range(of: "{count}") {
|
if let range = rawString.range(of: "{count}") {
|
||||||
@ -1186,7 +1186,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
title: AnyComponent(VStack([
|
title: AnyComponent(VStack([
|
||||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: "Allow Others to Mark as Done",
|
string: environment.strings.CreateTodo_AllowOthersToComplete,
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
textColor: theme.list.itemPrimaryTextColor
|
textColor: theme.list.itemPrimaryTextColor
|
||||||
)),
|
)),
|
||||||
@ -1209,7 +1209,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
title: AnyComponent(VStack([
|
title: AnyComponent(VStack([
|
||||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: "Allow Others to Add Tasks",
|
string: environment.strings.CreateTodo_AllowOthersToAppend,
|
||||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||||
textColor: theme.list.itemPrimaryTextColor
|
textColor: theme.list.itemPrimaryTextColor
|
||||||
)),
|
)),
|
||||||
|
@ -225,7 +225,7 @@ final class StarsParticlesView: UIView {
|
|||||||
} else {
|
} else {
|
||||||
particleLayer = SimpleLayer()
|
particleLayer = SimpleLayer()
|
||||||
particleLayer.contents = self.particleImage.cgImage
|
particleLayer.contents = self.particleImage.cgImage
|
||||||
particleLayer.bounds = CGRect(origin: CGPoint(), size: particleImage.size)
|
particleLayer.bounds = CGRect(origin: CGPoint(), size: self.particleImage.size)
|
||||||
self.particleLayers.append(particleLayer)
|
self.particleLayers.append(particleLayer)
|
||||||
self.layer.addSublayer(particleLayer)
|
self.layer.addSublayer(particleLayer)
|
||||||
}
|
}
|
||||||
|
@ -4809,16 +4809,18 @@ extension ChatControllerImpl {
|
|||||||
strongSelf.updateItemNodesHighlightedStates(animated: initial)
|
strongSelf.updateItemNodesHighlightedStates(animated: initial)
|
||||||
strongSelf.contentData?.scrolledToMessageIdValue = ScrolledToMessageId(id: mappedId, allowedReplacementDirection: [])
|
strongSelf.contentData?.scrolledToMessageIdValue = ScrolledToMessageId(id: mappedId, allowedReplacementDirection: [])
|
||||||
|
|
||||||
var hasQuote = false
|
var extendHighlight = false
|
||||||
if let quote = toSubject.quote {
|
if let quote = toSubject.quote {
|
||||||
if message.text.contains(quote.string) {
|
if message.text.contains(quote.string) {
|
||||||
hasQuote = true
|
extendHighlight = true
|
||||||
} else {
|
} else {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Chat_ToastQuoteNotFound, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Chat_ToastQuoteNotFound, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||||
}
|
}
|
||||||
|
} else if let _ = toSubject.todoTaskId {
|
||||||
|
extendHighlight = true
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(hasQuote ? 1.5 : 0.7, queue: Queue.mainQueue())).startStrict(completed: {
|
strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(extendHighlight ? 1.5 : 0.7, queue: Queue.mainQueue())).startStrict(completed: {
|
||||||
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
||||||
if controllerInteraction.highlightedState == highlightedState {
|
if controllerInteraction.highlightedState == highlightedState {
|
||||||
controllerInteraction.highlightedState = nil
|
controllerInteraction.highlightedState = nil
|
||||||
|
@ -16,6 +16,11 @@ import Pasteboard
|
|||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
private enum OptionsId: Hashable {
|
||||||
|
case item
|
||||||
|
case message
|
||||||
|
}
|
||||||
|
|
||||||
extension ChatControllerImpl {
|
extension ChatControllerImpl {
|
||||||
func openTodoItemContextMenu(todoItemId: Int32, params: ChatControllerInteraction.LongTapParams) -> Void {
|
func openTodoItemContextMenu(todoItemId: Int32, params: ChatControllerInteraction.LongTapParams) -> Void {
|
||||||
guard let message = params.message, let todo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }), let contentNode = params.contentNode else {
|
guard let message = params.message, let todo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }), let contentNode = params.contentNode else {
|
||||||
@ -24,16 +29,8 @@ extension ChatControllerImpl {
|
|||||||
|
|
||||||
let completion = todo.completions.first(where: { $0.id == todoItemId })
|
let completion = todo.completions.first(where: { $0.id == todoItemId })
|
||||||
|
|
||||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
// let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
// let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||||
|
|
||||||
let source: ContextContentSource
|
|
||||||
// if let location = location {
|
|
||||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
|
||||||
// } else {
|
|
||||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
var canMark = false
|
var canMark = false
|
||||||
if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) {
|
if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) {
|
||||||
@ -41,6 +38,12 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message)
|
let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message)
|
||||||
|
|
||||||
|
let _ = (contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] actions in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
if let completion {
|
if let completion {
|
||||||
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: completion.date, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
|
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: completion.date, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
|
||||||
@ -63,7 +66,7 @@ extension ChatControllerImpl {
|
|||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
|
|
||||||
if canMark {
|
if canMark {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Uncheck", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ContextMenu_UncheckTask, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -75,7 +78,7 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if canMark {
|
if canMark {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Check", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ContextMenu_CheckTask, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -87,8 +90,7 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Copy", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -105,7 +107,7 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !isReplyThreadHead {
|
if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !isReplyThreadHead {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -146,7 +148,7 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canEdit {
|
if canEdit {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Edit Item", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ContextMenu_EditTask, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -159,7 +161,7 @@ extension ChatControllerImpl {
|
|||||||
if todo.items.count > 1 {
|
if todo.items.count > 1 {
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Delete Item", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ContextMenu_DeleteTask, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -182,11 +184,38 @@ extension ChatControllerImpl {
|
|||||||
|
|
||||||
self.canReadHistory.set(false)
|
self.canReadHistory.set(false)
|
||||||
|
|
||||||
let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)
|
//TODO:localize
|
||||||
controller.dismissed = { [weak self] in
|
var sources: [ContextController.Source] = []
|
||||||
|
sources.append(
|
||||||
|
ContextController.Source(
|
||||||
|
id: AnyHashable(OptionsId.item),
|
||||||
|
title: self.presentationData.strings.Chat_Todo_ContextMenu_SectionTask,
|
||||||
|
source: .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)),
|
||||||
|
items: .single(ContextController.Items(content: .list(items)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
sources.append(
|
||||||
|
ContextController.Source(
|
||||||
|
id: AnyHashable(OptionsId.message),
|
||||||
|
title: self.presentationData.strings.Chat_Todo_ContextMenu_SectionList,
|
||||||
|
source: .extracted(ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false, keepDefaultContentTouches: false)),
|
||||||
|
items: .single(actions)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let contextController = ContextController(
|
||||||
|
presentationData: self.presentationData,
|
||||||
|
configuration: ContextController.Configuration(
|
||||||
|
sources: sources,
|
||||||
|
initialId: AnyHashable(OptionsId.item)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
contextController.dismissed = { [weak self] in
|
||||||
self?.canReadHistory.set(true)
|
self?.canReadHistory.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window?.presentInGlobalOverlay(controller)
|
self.window?.presentInGlobalOverlay(contextController)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4955,11 +4955,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
//TODO:localize
|
|
||||||
if !self.context.isPremium {
|
if !self.context.isPremium {
|
||||||
let controller = UndoOverlayController(
|
let controller = UndoOverlayController(
|
||||||
presentationData: self.presentationData,
|
presentationData: self.presentationData,
|
||||||
content: .premiumPaywall(title: nil, text: "Only [Telegram Premium]() subscribers can mark tasks as done.", customUndoText: nil, timeout: nil, linkAction: nil),
|
content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Todo_PremiumRequired, customUndoText: nil, timeout: nil, linkAction: nil),
|
||||||
action: { [weak self] action in
|
action: { [weak self] action in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return false
|
return false
|
||||||
@ -4979,7 +4978,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
let controller = UndoOverlayController(
|
let controller = UndoOverlayController(
|
||||||
presentationData: self.presentationData,
|
presentationData: self.presentationData,
|
||||||
content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: "\(peerName) has restricted others from editing this to do list.", customUndoText: nil, timeout: nil),
|
content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: self.presentationData.strings.Chat_Todo_CompletionLimited(peerName).string, customUndoText: nil, timeout: nil),
|
||||||
action: { _ in
|
action: { _ in
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -535,7 +535,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
private var chatHistoryLocationValue: ChatHistoryLocationInput? {
|
private var chatHistoryLocationValue: ChatHistoryLocationInput? {
|
||||||
didSet {
|
didSet {
|
||||||
if let chatHistoryLocationValue = self.chatHistoryLocationValue, chatHistoryLocationValue != oldValue {
|
if let chatHistoryLocationValue = self.chatHistoryLocationValue, chatHistoryLocationValue != oldValue {
|
||||||
chatHistoryLocationPromise.set(chatHistoryLocationValue)
|
self.chatHistoryLocationPromise.set(chatHistoryLocationValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,7 +215,8 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||||||
if case .center = position, highlight {
|
if case .center = position, highlight {
|
||||||
scrolledToIndex = scrollSubject
|
scrolledToIndex = scrollSubject
|
||||||
}
|
}
|
||||||
if case .center = position, let quote = scrollSubject.quote {
|
if case .center = position {
|
||||||
|
if let quote = scrollSubject.quote {
|
||||||
position = .center(.custom({ itemNode in
|
position = .center(.custom({ itemNode in
|
||||||
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
||||||
if let quoteRect = itemNode.getQuoteRect(quote: quote.string, offset: quote.offset) {
|
if let quoteRect = itemNode.getQuoteRect(quote: quote.string, offset: quote.offset) {
|
||||||
@ -224,6 +225,16 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||||||
}
|
}
|
||||||
return 0.0
|
return 0.0
|
||||||
}))
|
}))
|
||||||
|
} else if let todoTaskId = scrollSubject.todoTaskId {
|
||||||
|
position = .center(.custom({ itemNode in
|
||||||
|
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
||||||
|
if let taskRect = itemNode.getTodoTaskRect(id: todoTaskId) {
|
||||||
|
return taskRect.midY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var index = toView.filteredEntries.count - 1
|
var index = toView.filteredEntries.count - 1
|
||||||
for entry in toView.filteredEntries {
|
for entry in toView.filteredEntries {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user