Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
5180CB9127356D48001FC7EF /* MessageSubjectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */; };
5180CB9327357B67001FC7EF /* RawClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */; };
5180CB9527357BB0001FC7EF /* WkdApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9427357BB0001FC7EF /* WkdApi.swift */; };
5180CB97273724E9001FC7EF /* ThreadMessageSenderCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB96273724E9001FC7EF /* ThreadMessageSenderCellNode.swift */; };
518389C82726D7DD00131B2C /* UIViewController+Spinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518389C72726D7DD00131B2C /* UIViewController+Spinner.swift */; };
518389CA2726D8F700131B2C /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518389C92726D8F700131B2C /* UIApplicationExtension.swift */; };
51B0C7712729861C00124663 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0C7702729861C00124663 /* String+Extension.swift */; };
Expand Down Expand Up @@ -482,6 +483,7 @@
5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubjectNode.swift; sourceTree = "<group>"; };
5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawClientConfiguration.swift; sourceTree = "<group>"; };
5180CB9427357BB0001FC7EF /* WkdApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WkdApi.swift; sourceTree = "<group>"; };
5180CB96273724E9001FC7EF /* ThreadMessageSenderCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMessageSenderCellNode.swift; sourceTree = "<group>"; };
518389C72726D7DD00131B2C /* UIViewController+Spinner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Spinner.swift"; sourceTree = "<group>"; };
518389C92726D8F700131B2C /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = "<group>"; };
51B0C7702729861C00124663 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1966,6 +1968,7 @@
5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */,
9F82779B23737E2A00E19C07 /* MessageSubjectAndTimeNode.swift */,
9F82779D23737E3800E19C07 /* MessageTextSubjectNode.swift */,
5180CB96273724E9001FC7EF /* ThreadMessageSenderCellNode.swift */,
9F56BD3123438B5B00A7371A /* InboxCellNode.swift */,
9F56BD3523438B9D00A7371A /* TextCellNode.swift */,
D24ABA6223FDB4FF002EE9DD /* RecipientEmailsCellNode.swift */,
Expand Down Expand Up @@ -2746,6 +2749,7 @@
51DE2FEE2714DA0400916222 /* ContactKeyCellNode.swift in Sources */,
D2A9CA432426210200E1D898 /* SetupTitleNode.swift in Sources */,
D2F6D12F24324ACC00DB4065 /* SwitchCellNode.swift in Sources */,
5180CB97273724E9001FC7EF /* ThreadMessageSenderCellNode.swift in Sources */,
D2E26F6824F169E300612AF1 /* ContactCellNode.swift in Sources */,
D2A9CA38242618DF00E1D898 /* LinkButtonNode.swift in Sources */,
D24FAFA42520BF9100BF46C5 /* CheckBoxCircleView.swift in Sources */,
Expand Down
8 changes: 4 additions & 4 deletions FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@
import FlowCryptUI
import UIKit

extension TextImageNode.Input {
extension ThreadMessageSenderCellNode.Input {
init(threadMessage: ThreadDetailsViewController.Input) {
let sender = threadMessage.rawMessage.sender ?? "message_unknown_sender".localized
let date = DateFormatter().formatDate(threadMessage.rawMessage.date)
let isMessageRead = threadMessage.rawMessage.isMessageRead

let collapseImage = #imageLiteral(resourceName: "arrow_up").tinted(.white)
let expandImage = #imageLiteral(resourceName: "arrow_down").tinted(.white)
let image = threadMessage.isExpanded ? expandImage : collapseImage

let style: NSAttributedString.Style = isMessageRead
? .regular(17)
: .bold(17)
Expand All @@ -32,12 +28,16 @@ extension TextImageNode.Input {
: .mainTextUnreadColor

self.init(
title: NSAttributedString.text(from: sender, style: style, color: textColor),
subtitle: NSAttributedString.text(from: date, style: style, color: dateColor),
image: image,
imageSize: CGSize(width: 16, height: 16),
nodeInsets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8),
backgroundColor: .backgroundColor
sender: NSAttributedString.text(from: sender, style: style, color: textColor),
date: NSAttributedString.text(from: date, style: style, color: dateColor),
isExpanded: threadMessage.isExpanded,
buttonColor: .messageButtonColor
)
}
}

extension UIColor {
static var messageButtonColor: UIColor {
.colorFor(darkStyle: .white, lightStyle: .main)
}
}
45 changes: 32 additions & 13 deletions FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,23 @@ extension ThreadDetailsViewController {
private func expandThreadMessage() {
let indexOfSectionToExpand = thread.messages.firstIndex(where: { $0.isMessageRead == false }) ?? input.count - 1
let indexPath = IndexPath(row: 0, section: indexOfSectionToExpand + 1)
handleTap(at: indexPath)
handleExpandTap(at: indexPath)
}

private func handleTap(at indexPath: IndexPath) {
guard let threadNode = node.nodeForRow(at: indexPath) as? TextImageNode else {
private func handleExpandTap(at indexPath: IndexPath) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is much better - thank you!

guard let threadNode = node.nodeForRow(at: indexPath) as? ThreadMessageSenderCellNode else {
logger.logError("Fail to handle tap at \(indexPath)")
return
}

UIView.animate(
withDuration: 0.3,
animations: {
threadNode.imageNode.view.transform = CGAffineTransform(rotationAngle: .pi)
threadNode.replyNode.view.alpha = self.input[indexPath.section-1].isExpanded ? 0 : 1
threadNode.expandNode.view.transform = CGAffineTransform(rotationAngle: .pi)
},
completion: { [weak self] _ in
guard let self = self else {
return
}
guard let self = self else { return }

if let processedMessage = self.input[indexPath.section-1].processedMessage {
self.handleReceived(message: processedMessage, at: indexPath)
Expand All @@ -118,6 +117,28 @@ extension ThreadDetailsViewController {
)
}

private func handleReplyTap(at indexPath: IndexPath) {
guard let email = DataService.shared.email,
let input = input[safe: indexPath.section-1],
let processedMessage = input.processedMessage
else { return }

let replyInfo = ComposeMessageInput.ReplyInfo(
recipient: input.rawMessage.sender,
subject: input.rawMessage.subject,
mime: processedMessage.rawMimeData,
sentDate: input.rawMessage.date,
message: processedMessage.text,
threadId: input.rawMessage.threadId
)

let composeInput = ComposeMessageInput(type: .reply(replyInfo))
navigationController?.pushViewController(
ComposeViewController(email: email, input: composeInput),
animated: true
)
}

private func markAsRead(at index: Int) {
guard let message = input[safe: index]?.rawMessage else {
return
Expand Down Expand Up @@ -344,11 +365,9 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource {
let section = self.input[indexPath.section-1]

if indexPath.row == 0 {
return TextImageNode(
return ThreadMessageSenderCellNode(
input: .init(threadMessage: section),
onTap: { [weak self] _ in
self?.handleTap(at: indexPath)
}
onReplyTap: { [weak self] _ in self?.handleReplyTap(at: indexPath) }
)
}

Expand All @@ -371,10 +390,10 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource {
}

func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) {
guard tableNode.nodeForRow(at: indexPath) is TextImageNode else {
guard tableNode.nodeForRow(at: indexPath) is ThreadMessageSenderCellNode else {
return
}
handleTap(at: indexPath)
handleExpandTap(at: indexPath)
}
}

Expand Down
2 changes: 1 addition & 1 deletion FlowCryptUI/Cell Nodes/MessageSubjectNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public final class MessageSubjectNode: CellNode {
public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec {
subjectNode.style.flexGrow = 1.0
return ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 16, left: 8, bottom: 4, right: 8),
insets: UIEdgeInsets(top: 16, left: 16, bottom: 4, right: 16),
child: subjectNode
)
}
Expand Down
2 changes: 1 addition & 1 deletion FlowCryptUI/Cell Nodes/MessageTextSubjectNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final class MessageTextSubjectNode: CellNode {
public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec {
textNode.style.flexGrow = 1.0
return ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8),
insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16),
child: textNode
)
}
Expand Down
111 changes: 111 additions & 0 deletions FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// ThreadMessageSenderCellNode.swift
// FlowCryptUI
//
// Created by Roma Sosnovsky on 06/11/21
// Copyright © 2017-present FlowCrypt a. s. All rights reserved.
//

import AsyncDisplayKit
import UIKit

public final class ThreadMessageSenderCellNode: CellNode {
public struct Input {
public let sender: NSAttributedString
public let date: NSAttributedString?
public let isExpanded: Bool
public let buttonColor: UIColor

public init(sender: NSAttributedString,
date: NSAttributedString,
isExpanded: Bool,
buttonColor: UIColor) {
self.sender = sender
self.date = date
self.isExpanded = isExpanded
self.buttonColor = buttonColor
}

var replyImage: UIImage? {
return createButtonImage(systemName: "arrowshape.turn.up.left")
}
var expandImage: UIImage? {
let systemName = isExpanded ? "chevron.up" : "chevron.down"
return createButtonImage(systemName: systemName)
}

private func createButtonImage(systemName: String, pointSize: CGFloat = 18) -> UIImage? {
let configuration = UIImage.SymbolConfiguration(pointSize: pointSize)
return UIImage(systemName: systemName, withConfiguration: configuration)
}
}

private let senderNode = ASTextNode2()
private let dateNode = ASTextNode2()
public private(set) var replyNode = ASButtonNode()
public private(set) var expandNode = ASImageNode()

private let input: ThreadMessageSenderCellNode.Input
private var onReplyTap: ((ThreadMessageSenderCellNode) -> Void)?

public init(input: ThreadMessageSenderCellNode.Input,
onReplyTap: ((ThreadMessageSenderCellNode) -> Void)?) {
self.input = input
self.onReplyTap = onReplyTap
super.init()
automaticallyManagesSubnodes = true

senderNode.attributedText = input.sender
dateNode.attributedText = input.date

setupReplyNode()
setupExpandNode()
}

private func setupReplyNode() {
replyNode.setImage(input.replyImage, for: .normal)
replyNode.imageNode.imageModificationBlock = ASImageNodeTintColorModificationBlock(input.buttonColor)
replyNode.contentMode = .center
replyNode.alpha = input.isExpanded ? 1 : 0
replyNode.addTarget(self, action: #selector(onReplyNodeTap), forControlEvents: .touchUpInside)
}

private func setupExpandNode() {
expandNode.image = input.expandImage
expandNode.imageModificationBlock = ASImageNodeTintColorModificationBlock(input.buttonColor)
expandNode.contentMode = .right
}

@objc private func onReplyNodeTap() {
onReplyTap?(self)
}

public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec {
replyNode.style.preferredSize = CGSize(width: 44, height: 44)
expandNode.style.preferredSize = CGSize(width: 18, height: 44)

let infoNode = ASStackLayoutSpec(
direction: .vertical,
spacing: 4,
justifyContent: .start,
alignItems: .start,
children: [senderNode, dateNode]
)

infoNode.style.flexGrow = 1
infoNode.style.flexShrink = 1

let contentSpec = ASStackLayoutSpec(
direction: .horizontal,
spacing: 4,
justifyContent: .spaceBetween,
alignItems: .center,
children: [infoNode, replyNode, expandNode]
)

return ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 12),
child: contentSpec
)
}
}