Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: External tag #945

Merged
merged 20 commits into from
Aug 29, 2023
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
27 changes: 21 additions & 6 deletions Mail/Components/RecipientChipLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@

import Foundation
import MailCore
import MailResources
import SwiftUI
import UIKit

struct RecipientChipLabelView: UIViewRepresentable {
@Environment(\.isEnabled) private var isEnabled: Bool
@EnvironmentObject private var mailboxManager: MailboxManager

let recipient: Recipient
var removeHandler: (() -> Void)?
var switchFocusHandler: (() -> Void)?

func makeUIView(context: Context) -> RecipientChipLabel {
let label = RecipientChipLabel(recipient: recipient)
let label = RecipientChipLabel(recipient: recipient, external: recipient.isExternal(mailboxManager: mailboxManager))
label.removeHandler = removeHandler
label.switchFocusHandler = switchFocusHandler
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
Expand All @@ -40,7 +42,9 @@ struct RecipientChipLabelView: UIViewRepresentable {

func updateUIView(_ uiLabel: RecipientChipLabel, context: Context) {
uiLabel.text = recipient.name.isEmpty ? recipient.email : recipient.name
uiLabel.isExternal = recipient.isExternal(mailboxManager: mailboxManager)
uiLabel.isUserInteractionEnabled = isEnabled
uiLabel.updateColors(isFirstResponder: uiLabel.isFirstResponder)
}
}

Expand All @@ -58,19 +62,23 @@ class RecipientChipLabel: UILabel, UIKeyInput {
override var canBecomeFirstResponder: Bool { return isUserInteractionEnabled }

var hasText = false
var isExternal = false

init(recipient: Recipient) {
init(recipient: Recipient, external: Bool) {
super.init(frame: .zero)

text = recipient.name.isEmpty ? recipient.email : recipient.name
textAlignment = .center
numberOfLines = 1

font = .systemFont(ofSize: 16)
updateColors(isFirstResponder: false)

layer.cornerRadius = intrinsicContentSize.height / 2
layer.borderWidth = 1
layer.masksToBounds = true

isExternal = external
updateColors(isFirstResponder: false)
}

@available(*, unavailable)
Expand Down Expand Up @@ -102,8 +110,15 @@ class RecipientChipLabel: UILabel, UIKeyInput {
removeHandler?()
}

private func updateColors(isFirstResponder: Bool) {
textColor = isFirstResponder ? UserDefaults.shared.accentColor.secondary.color : .tintColor
backgroundColor = isFirstResponder ? .tintColor : UserDefaults.shared.accentColor.secondary.color
public func updateColors(isFirstResponder: Bool) {
if isExternal {
textColor = isFirstResponder ? MailResourcesAsset.onTagColor.color : MailResourcesAsset.textPrimaryColor.color
borderColor = MailResourcesAsset.yellowColor.color
backgroundColor = isFirstResponder ? MailResourcesAsset.yellowColor.color : MailResourcesAsset.textFieldColor.color
} else {
textColor = isFirstResponder ? UserDefaults.shared.accentColor.secondary.color : .tintColor
borderColor = isFirstResponder ? UserDefaults.shared.accentColor.primary.color : UserDefaults.shared.accentColor.secondary.color
backgroundColor = isFirstResponder ? .tintColor : UserDefaults.shared.accentColor.secondary.color
}
}
}
4 changes: 3 additions & 1 deletion Mail/Helpers/PreviewHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ enum PreviewHelper {
mdaVersion: "",
isLimited: false,
isFree: false,
dailyLimit: 999)
dailyLimit: 999,
aliases: ["test@example.com", "test@example.ch"].toRealmList(),
externalMailFlagEnabled: true)

static let sampleFolder = Folder(id: "",
path: "Folder",
Expand Down
60 changes: 60 additions & 0 deletions Mail/Views/Alerts/ExternalRecipientView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Infomaniak Mail - iOS App
Copyright (C) 2022 Infomaniak Network SA

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import MailCore
import MailResources
import RealmSwift
import SwiftUI

struct ExternalRecipientView: View {
@Environment(\.dismiss) private var dismiss

public var externalTagSate: DisplayExternalRecipientStatus.State
public var isDraft: Bool

var body: some View {
VStack(alignment: .leading, spacing: 24) {
isDraft ? Text(MailResourcesStrings.Localizable.externalDialogTitleRecipient)
.textStyle(.bodyMedium) : Text(MailResourcesStrings.Localizable.externalDialogTitleExpeditor)
.textStyle(.bodyMedium)

switch externalTagSate {
case .none:
EmptyView()
case .one(let recipient):
isDraft ? Text(MailResourcesStrings.Localizable.externalDialogDescriptionRecipient(recipient.email))
.textStyle(.bodySecondary) : Text(MailResourcesStrings.Localizable.externalDialogDescriptionExpeditor(recipient.email))
.textStyle(.bodySecondary)
case .many:
isDraft ? Text(MailResourcesStrings.Localizable.externalDialogDescriptionRecipientPlural)
.textStyle(.bodySecondary) : Text(MailResourcesStrings.Localizable.externalDialogDescriptionExpeditorPlural)
.textStyle(.bodySecondary)
}

ModalButtonsView(primaryButtonTitle: MailResourcesStrings.Localizable.externalDialogConfirmButton, secondaryButtonTitle: nil) {
dismiss()
}
}
}
}

struct ExternalRecipientView_Previews: PreviewProvider {
static var previews: some View {
ExternalRecipientView(externalTagSate: .many, isDraft: false)
}
}
54 changes: 53 additions & 1 deletion Mail/Views/New Message/ComposeMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class NewMessageAlert: SheetState<NewMessageAlert.State> {
enum State {
case link(handler: (String) -> Void)
case emptySubject(handler: () -> Void)
case externalRecipient(state: DisplayExternalRecipientStatus.State)
}
}

Expand All @@ -72,6 +73,7 @@ struct ComposeMessageView: View {
@State private var editorFocus = false
@State private var currentSignature: Signature?
@State private var initialAttachments = [Attachable]()
@State private var isShowingExternalTag = true

@State private var editorModel = RichTextEditorModel()
@State private var scrollView: UIScrollView?
Expand Down Expand Up @@ -159,6 +161,8 @@ struct ComposeMessageView: View {
AddLinkView(actionHandler: handler)
case .emptySubject(let handler):
EmptySubjectView(actionHandler: handler)
case .externalRecipient(let state):
ExternalRecipientView(externalTagSate: state, isDraft: true)
case .none:
EmptyView()
}
Expand Down Expand Up @@ -240,6 +244,46 @@ struct ComposeMessageView: View {
.disabled(isSendButtonDisabled)
}
}
.safeAreaInset(edge: .bottom) {
if isShowingExternalTag {
let externalTag = draft.displayExternalTag(mailboxManager: mailboxManager)
switch externalTag {
case .many, .one:
HStack(spacing: 24) {
Text(MailResourcesStrings.Localizable.externalDialogTitleRecipient)
.foregroundColor(MailResourcesAsset.onTagColor)
.textStyle(.bodySmall)

Spacer()

Button {
matomo.track(eventWithCategory: .externals, name: "bannerInfo")
alert.state = .externalRecipient(state: externalTag)
} label: {
MailResourcesAsset.info.swiftUIImage
.resizable()
.foregroundColor(MailResourcesAsset.onTagColor)
.frame(width: 16, height: 16)
}

Button {
matomo.track(eventWithCategory: .externals, name: "bannerManuallyClosed")
isShowingExternalTag = false
} label: {
MailResourcesAsset.closeSmall.swiftUIImage
.resizable()
.foregroundColor(MailResourcesAsset.onTagColor)
.frame(width: 16, height: 16)
}
}
.frame(maxWidth: .infinity)
.padding(16)
.background(MailResourcesAsset.yellowColor.swiftUIColor)
case .none:
EmptyView()
}
}
}
}

/// Progress view
Expand Down Expand Up @@ -275,7 +319,15 @@ struct ComposeMessageView: View {
}

private func sendDraft() {
matomo.trackSendMessage(numberOfTo: draft.to.count, numberOfCc: draft.cc.count, numberOfBcc: draft.bcc.count)
let sentWithExternals: Bool
switch draft.displayExternalTag(mailboxManager: mailboxManager) {
case .one, .many:
sentWithExternals = true
case .none:
sentWithExternals = false
}

matomo.trackSendMessage(draft: draft, sentWithExternals: sentWithExternals)
if let liveDraft = draft.thaw() {
try? liveDraft.realm?.write {
liveDraft.action = .send
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ struct ComposeMessageCellRecipients: View {
}

withAnimation {
recipient.isAddedByMe = true
$recipients.append(recipient)
}
textDebounce.text = ""
Expand Down
3 changes: 3 additions & 0 deletions Mail/Views/New Message/Recipients/FullRecipientsList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ struct FullRecipientsList: View {
@MainActor private func remove(recipientAt: Int) {
@InjectService var matomo: MatomoUtils
matomo.track(eventWithCategory: .newMessage, name: "deleteRecipient")
if recipients[recipientAt].isExternal(mailboxManager: mailboxManager) {
matomo.track(eventWithCategory: .externals, name: "deleteRecipient")
}

withAnimation {
$recipients.remove(at: recipientAt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ struct SettingsNotificationsView: View {
func updateTopicsForCurrentUserIfNeeded() {
Task {
guard let subscribedTopics else { return }

await notificationService.updateTopicsIfNeeded(subscribedTopics, userApiFetcher: mailboxManager.apiFetcher)
}
}
Expand Down
46 changes: 38 additions & 8 deletions Mail/Views/Thread/ThreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct ThreadView: View {
@State private var displayNavigationTitle = false
@State private var replyOrReplyAllMessage: Message?

@StateObject private var alert = NewMessageAlert()

@ObservedRealmObject var thread: Thread

private let toolbarActions: [Action] = [.reply, .forward, .archive, .delete]
Expand All @@ -60,14 +62,34 @@ struct ThreadView: View {
}
.frame(width: 0, height: 0)

Text(thread.formattedSubject)
.textStyle(.header2)
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)
.lineSpacing(8)
.padding(.top, 8)
.padding(.bottom, 16)
.padding(.horizontal, 16)
VStack(alignment: .leading, spacing: 8) {
Text(thread.formattedSubject)
.textStyle(.header2)
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)
.lineSpacing(8)

let externalTag = thread.displayExternalRecipientState(mailboxManager: mailboxManager, recipientsList: thread.from)
switch externalTag {
case .many, .one:
Button {
matomo.track(eventWithCategory: .externals, name: "threadTag")
alert.state = .externalRecipient(state: externalTag)
} label: {
Text(MailResourcesStrings.Localizable.externalTag)
.foregroundColor(MailResourcesAsset.onTagColor)
.textStyle(.labelMedium)
.padding(4)
.background(MailResourcesAsset.yellowColor.swiftUIColor)
.cornerRadius(2)
}
case .none:
EmptyView()
}
}
.padding(.top, 8)
.padding(.bottom, 16)
.padding(.horizontal, 16)

MessageListView(messages: thread.messages)
}
Expand Down Expand Up @@ -147,6 +169,14 @@ struct ThreadView: View {
// Dismiss on iPhone only
dismiss()
}
.customAlert(isPresented: $alert.isShowing) {
switch alert.state {
case .externalRecipient(let state):
ExternalRecipientView(externalTagSate: state, isDraft: false)
default:
EmptyView()
}
}
.matomoView(view: [MatomoUtils.View.threadView.displayName, "Main"])
}

Expand Down
2 changes: 1 addition & 1 deletion MailCore/API/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public extension Endpoint {
static var mailboxes: Endpoint {
return .base.appending(
path: "/mailbox",
queryItems: [URLQueryItem(name: "with", value: "unseen")]
queryItems: [URLQueryItem(name: "with", value: "unseen,aliases,external_mail_flag_enabled")]
)
}

Expand Down
2 changes: 1 addition & 1 deletion MailCore/Cache/MailboxManager/MailboxManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public final class MailboxManager: ObservableObject, MailboxManageable {
let realmName = "\(mailbox.userId)-\(mailbox.mailboxId).realm"
realmConfiguration = Realm.Configuration(
fileURL: MailboxManager.constants.rootDocumentsURL.appendingPathComponent(realmName),
schemaVersion: 18,
schemaVersion: 19,
migrationBlock: { migration, oldSchemaVersion in
// No migration needed from 0 to 16
if oldSchemaVersion < 17 {
Expand Down
16 changes: 16 additions & 0 deletions MailCore/Models/Draft.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,19 @@ public extension Draft {
return available
}
}

public extension Draft {
/// Compute if the draft has external recipients
func displayExternalTag(mailboxManager: MailboxManager) -> DisplayExternalRecipientStatus.State {
var recipientsList = List<Recipient>()
recipientsList.append(objectsIn: cc)
recipientsList.append(objectsIn: bcc)
recipientsList.append(objectsIn: to)
return displayExternalRecipientState(mailboxManager: mailboxManager, recipientsList: recipientsList)
}

func displayExternalRecipientState(mailboxManager: MailboxManager, recipientsList: List<Recipient>) -> DisplayExternalRecipientStatus.State {
let externalDisplayStatus = DisplayExternalRecipientStatus(mailboxManager: mailboxManager, recipientsList: recipientsList)
return externalDisplayStatus.state
}
}
Loading
Loading