Various fixes

This commit is contained in:
Ilya Laktyushin 2025-06-26 14:09:26 +02:00
parent dbda23b1e1
commit 3b46e1d3d0
10 changed files with 91 additions and 1102 deletions

View File

@ -14466,7 +14466,9 @@ Sorry for the inconvenience.";
"Premium.Todo" = "Checklists";
"Premium.TodoInfo" = "Plan, assign and complete tasks seamlessly and efficiently.";
"CreateTodo.Title" = "Title";
"CreateTodo.Title" = "New Checklist";
"CreateTodo.AddTitle" = "Add a Task";
"CreateTodo.EditTitle" = "Edit Checklist";
"CreateTodo.TodoTitle" = "CHECKLIST";
"CreateTodo.TitlePlaceholder" = "Title";
"CreateTodo.TaskPlaceholder" = "Task";
@ -14474,6 +14476,8 @@ Sorry for the inconvenience.";
"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.Save" = "Save";
"CreateTodo.Send" = "Send";
"CreateTodo.AllowOthersToComplete" = "Allow Others to Mark as Done";
"CreateTodo.AllowOthersToAppend" = "Allow Others to Add Tasks";
@ -14499,3 +14503,5 @@ Sorry for the inconvenience.";
"Bot.AddToGroup.Title" = "Add to Group";
"Bot.AddToChannel.Title" = "Add to Channel";
"ScheduledMessages.TodoUnavailable" = "Voting will become available after the message is published.";

View File

@ -234,7 +234,7 @@ final class ComposePollScreenComponent: Component {
private func item(at point: CGPoint) -> (AnyHashable, ComponentView<Empty>)? {
let localPoint = self.pollOptionsSectionContainer.convert(point, from: self)
for (id, itemView) in self.pollOptionsSectionContainer.itemViews {
if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed {
if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed && !view.currentText.isEmpty {
let viewFrame = view.convert(view.bounds, to: self.pollOptionsSectionContainer)
let iconFrame = CGRect(origin: CGPoint(x: viewFrame.maxX - viewFrame.height, y: viewFrame.minY), size: CGSize(width: viewFrame.height, height: viewFrame.height))
if iconFrame.contains(localPoint) {

View File

@ -204,6 +204,13 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor
}
}
signals.append(.single(.done(.text(text))))
} else if let mediaReference = item.mediaReference, let todo = mediaReference.media as? TelegramMediaTodo {
var text = "☑️ \(todo.text)"
for item in todo.items {
let completed = todo.completions.contains(where: { $0.id == item.id })
text.append("\n\(completed ? "+" : "-") \(item.text)")
}
signals.append(.single(.done(.text(text))))
} else if let mediaReference = item.mediaReference, let contact = mediaReference.media as? TelegramMediaContact {
let contactData: DeviceContactExtendedData
if let vCard = contact.vCardData, let vCardData = vCard.data(using: .utf8), let parsed = DeviceContactExtendedData(vcard: vCardData) {

View File

@ -1351,11 +1351,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributes[1] = boldAttributes
let resultString: PresentationStrings.FormattedString
if completed.count > 1 {
if completed.count > 1 || (completed.count == 1 && taskTitle == nil) {
resultString = strings.Notification_TodoMultipleCompleted(peerName, strings.Notification_TodoTasks(Int32(completed.count)))
} else if let _ = completed.first {
resultString = strings.Notification_TodoCompleted(peerName, taskTitle ?? "")
} else if incompleted.count > 1 {
} else if incompleted.count > 1 || (incompleted.count == 1 && taskTitle == nil) {
resultString = strings.Notification_TodoMultipleIncompleted(peerName, strings.Notification_TodoTasks(Int32(incompleted.count)))
} else if let _ = incompleted.first {
resultString = strings.Notification_TodoIncompleted(peerName, taskTitle ?? "")

View File

@ -569,39 +569,19 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
}
}
// fileprivate var absoluteRect: (CGRect, CGSize)?
// fileprivate func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
// self.absoluteRect = (rect, containerSize)
// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else {
// return
// }
// guard !self.sourceNode.isExtractedToContextPreview else {
// return
// }
// let mappedRect = CGRect(origin: CGPoint(x: rect.minX + backgroundWallpaperNode.frame.minX, y: rect.minY + backgroundWallpaperNode.frame.minY), size: rect.size)
// backgroundWallpaperNode.update(rect: mappedRect, within: containerSize)
// }
//
// fileprivate func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else {
// return
// }
// backgroundWallpaperNode.offset(value: value, animationCurve: animationCurve, duration: duration)
// }
//
// fileprivate func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else {
// return
// }
// backgroundWallpaperNode.offsetSpring(value: value, duration: duration, damping: damping)
// }
@objc private func buttonPressed() {
guard !self.ignoreNextTap else {
self.ignoreNextTap = false
return
}
if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, self.canMark, self.isPremium {
var isScheduledMessages = false
if let message = self.message, Namespaces.Message.allScheduled.contains(message.id.namespace) {
isScheduledMessages = true
}
let canUpdate = self.canMark && self.isPremium && !isScheduledMessages
if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, canUpdate {
radioNode.updateIsChecked(!isChecked, animated: true)
self.selectionUpdated?()
} else {
@ -616,7 +596,10 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
return { context, presentationData, presentationContext, message, todo, option, completion, translation, constrainedWidth in
var canMark = false
if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) {
canMark = true
if let forwardInfo = message.forwardInfo, let authorId = forwardInfo.author?.id, authorId != context.account.peerId {
} else {
canMark = true
}
}
let leftInset: CGFloat = canMark ? 57.0 : 29.0

View File

@ -57,7 +57,7 @@ final class ComposeTodoScreenComponent: Component {
let id: Int32
let textInputState = TextFieldComponent.ExternalState()
let textFieldTag = NSObject()
var resetText: String?
var resetText: NSAttributedString?
init(id: Int32) {
self.id = id
@ -87,7 +87,7 @@ final class ComposeTodoScreenComponent: Component {
private let todoTextInputState = TextFieldComponent.ExternalState()
private let todoTextFieldTag = NSObject()
private var resetTodoText: String?
private var resetTodoText: NSAttributedString?
private var nextTodoItemId: Int32 = 1
private var todoItems: [TodoItem] = []
@ -182,7 +182,7 @@ final class ComposeTodoScreenComponent: Component {
private func item(at point: CGPoint) -> (AnyHashable, ComponentView<Empty>)? {
let localPoint = self.todoItemsSectionContainer.convert(point, from: self)
for (id, itemView) in self.todoItemsSectionContainer.itemViews {
if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed {
if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed && !view.currentText.isEmpty {
let viewFrame = view.convert(view.bounds, to: self.todoItemsSectionContainer)
let iconFrame = CGRect(origin: CGPoint(x: viewFrame.maxX - viewFrame.height, y: viewFrame.minY), size: CGSize(width: viewFrame.height, height: viewFrame.height))
if iconFrame.contains(localPoint) {
@ -591,13 +591,13 @@ final class ComposeTodoScreenComponent: Component {
let isFirstTime = self.component == nil
if self.component == nil {
if let existingTodo = component.initialData.existingTodo {
self.resetTodoText = existingTodo.text
self.resetTodoText = chatInputStateStringWithAppliedEntities(existingTodo.text, entities: existingTodo.textEntities)
for item in existingTodo.items {
let todoItem = ComposeTodoScreenComponent.TodoItem(
id: item.id
)
todoItem.resetText = item.text
todoItem.resetText = chatInputStateStringWithAppliedEntities(item.text, entities: item.entities)
self.todoItems.append(todoItem)
}
self.nextTodoItemId = (existingTodo.items.max(by: { $0.id < $1.id })?.id ?? 0) + 1
@ -777,7 +777,7 @@ final class ComposeTodoScreenComponent: Component {
strings: environment.strings,
isEnabled: canEdit,
resetText: self.resetTodoText.flatMap { resetText in
return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText))
return ListComposePollOptionComponent.ResetText(value: resetText)
},
assumeIsEditing: self.inputMediaNodeTargetTag === self.todoTextFieldTag,
characterLimit: component.initialData.maxTodoTextLength,
@ -860,7 +860,7 @@ final class ComposeTodoScreenComponent: Component {
strings: environment.strings,
isEnabled: isEnabled,
resetText: todoItem.resetText.flatMap { resetText in
return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText))
return ListComposePollOptionComponent.ResetText(value: resetText)
},
assumeIsEditing: self.inputMediaNodeTargetTag === todoItem.textFieldTag,
characterLimit: component.initialData.maxTodoItemLength,
@ -931,12 +931,12 @@ final class ComposeTodoScreenComponent: Component {
var i = 0
for line in lines {
if i < self.todoItems.count {
self.todoItems[i].resetText = line
self.todoItems[i].resetText = NSAttributedString(string: line)
} else {
let todoItem = ComposeTodoScreenComponent.TodoItem(
id: self.nextTodoItemId
)
todoItem.resetText = line
todoItem.resetText = NSAttributedString(string: line)
self.todoItems.append(todoItem)
self.nextTodoItemId += 1
}
@ -1641,14 +1641,14 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if !initialData.canEdit && initialData.existingTodo != nil {
self.title = "Add a Task"
self.title = presentationData.strings.CreateTodo_Title
} else {
self.title = initialData.existingTodo != nil ? "Edit To Do List" : "New To Do List"
self.title = initialData.existingTodo != nil ? presentationData.strings.CreateTodo_EditTitle : presentationData.strings.CreateTodo_Title
}
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
let sendButtonItem = UIBarButtonItem(title: initialData.existingTodo != nil ? "Save" : presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed))
let sendButtonItem = UIBarButtonItem(title: initialData.existingTodo != nil ? presentationData.strings.CreateTodo_Save : presentationData.strings.CreateTodo_Send, style: .done, target: self, action: #selector(self.sendPressed))
self.sendButtonItem = sendButtonItem
self.navigationItem.setRightBarButton(sendButtonItem, animated: false)
sendButtonItem.isEnabled = false

View File

@ -73,6 +73,24 @@ extension ChatControllerImpl {
return
}
if !self.context.isPremium {
let controller = UndoOverlayController(
presentationData: self.presentationData,
content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Todo_PremiumRequired, customUndoText: nil, timeout: nil, linkAction: nil),
action: { [weak self] action in
guard let self else {
return false
}
if case .info = action {
let controller = self.context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
self.push(controller)
}
return false
}
)
self.present(controller, in: .current)
}
let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [], incompletedIds: [todoItemId]).start()
})))
}
@ -85,6 +103,24 @@ extension ChatControllerImpl {
return
}
if !self.context.isPremium {
let controller = UndoOverlayController(
presentationData: self.presentationData,
content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Todo_PremiumRequired, customUndoText: nil, timeout: nil, linkAction: nil),
action: { [weak self] action in
guard let self else {
return false
}
if case .info = action {
let controller = self.context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
self.push(controller)
}
return false
}
)
self.present(controller, in: .current)
}
let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [todoItemId], incompletedIds: []).start()
})))
}

View File

@ -4925,6 +4925,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
self.dismissAllTooltips()
guard self.presentationInterfaceState.subject != .scheduledMessages else {
self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.ScheduledMessages_TodoUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
if !self.context.isPremium {
let controller = UndoOverlayController(
presentationData: self.presentationData,

View File

@ -1573,12 +1573,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
if let activeTodo {
var maxTodoItemsCount: Int = 30
if let data = context.currentAppConfiguration.with({ $0 }).data {
if let value = data["todo_items_max"] as? Double {
maxTodoItemsCount = Int(value)
}
}
var canAppend = false
if message.author?.id == context.account.peerId || activeTodo.flags.contains(.othersCanAppend) {
if activeTodo.items.count < maxTodoItemsCount && (activeTodo.flags.contains(.othersCanAppend) || message.author?.id == context.account.peerId) {
canAppend = true
}
if canAppend {
actions.append(.action(ContextMenuActionItem(text: "Add a Task", icon: { theme in
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Chat_Todo_ContextMenu_AddTask, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
interfaceInteraction.editTodoMessage(messages[0].id, nil, true)