Skip to content

Commit

Permalink
Merge branch 'master' into refactor-panels
Browse files Browse the repository at this point in the history
# Conflicts:
#	Mail/Views/Thread List/ThreadListView.swift
#	MailCore/UI/UIConstants.swift
#	Project.swift
  • Loading branch information
PhilippeWeidmann committed Apr 27, 2023
2 parents 303b2da + 8a547ce commit 44b1719
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 119 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
build:
name: Build and Test project
runs-on: self-hosted
runs-on: [ self-hosted, iOS ]

steps:
- name: Cancel Previous Runs
Expand Down
22 changes: 20 additions & 2 deletions .package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@
"version" : "7.5.2"
}
},
{
"identity" : "navigationbackport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/johnpatrickmorgan/NavigationBackport",
"state" : {
"revision" : "5096dda355148dd40162810e7f56292ce0a2b09b",
"version" : "0.7.5"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
Expand All @@ -143,6 +152,15 @@
"version" : "12.1.0"
}
},
{
"identity" : "popovers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/aheze/Popovers",
"state" : {
"revision" : "de44c4dd7271ec6413fe350f7efadb14e5e18dce",
"version" : "1.3.2"
}
},
{
"identity" : "realm-core",
"kind" : "remoteSourceControl",
Expand All @@ -166,8 +184,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/getsentry/sentry-cocoa",
"state" : {
"revision" : "ac224c437a3070ffe34460137ac8761eddaf2852",
"version" : "8.3.3"
"revision" : "92a6472efc750a4e18bdee21c204942ab0bc4dcd",
"version" : "8.4.0"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
import MailCore
import SwiftUI

struct RecipientAutocompletionCell: View {
struct RecipientCell: View {
let recipient: Recipient

var body: some View {
HStack {
HStack(spacing: 8) {
AvatarView(avatarDisplayable: recipient, size: 40)
.accessibilityHidden(true)

Expand All @@ -38,17 +38,17 @@ struct RecipientAutocompletionCell: View {
.textStyle(.bodySecondary)
}
}
Spacer()
}
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
}
}

struct RecipientAutocompletionCell_Previews: PreviewProvider {
static var previews: some View {
RecipientAutocompletionCell(recipient: PreviewHelper.sampleRecipient1)
RecipientAutocompletionCell(recipient: PreviewHelper.sampleRecipient3)
RecipientCell(recipient: PreviewHelper.sampleRecipient1)
RecipientCell(recipient: PreviewHelper.sampleRecipient3)
}
}
77 changes: 77 additions & 0 deletions Mail/Components/RecipientChip.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
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 InfomaniakCoreUI
import MailCore
import MailResources
import Popovers
import SwiftUI

struct RecipientChip: View {
@Environment(\.window) private var window
@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor

let recipient: Recipient
let fieldType: ComposeViewFieldType
@FocusState var focusedField: ComposeViewFieldType?
let removeHandler: () -> Void
let switchFocusHandler: () -> Void

var body: some View {
Templates.Menu {
$0.width = nil
$0.originAnchor = .topLeft
$0.popoverAnchor = .topLeft
} content: {
RecipientCell(recipient: recipient)
.padding(.vertical, 8)
.padding(.horizontal, 16)
.frame(maxWidth: min(304, 0.8 * (window?.screen.bounds.width ?? 304)))

Templates.MenuButton(text: Text(MailResourcesStrings.Localizable.contactActionCopyEmailAddress),
image: MailResourcesAsset.duplicate.swiftUIImage) {
UIPasteboard.general.string = recipient.email
IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.snackbarEmailCopiedToClipboard)
}

Templates.MenuButton(text: Text(MailResourcesStrings.Localizable.actionDelete),
image: MailResourcesAsset.bin.swiftUIImage) {
removeHandler()
}
} label: { isSelected in
RecipientChipLabelView(recipient: recipient, removeHandler: removeAndFocus, switchFocusHandler: switchFocusHandler)
.fixedSize()
.opacity(isSelected ? 0.8 : 1)
}
}

private func removeAndFocus() {
focusedField = fieldType
removeHandler()
}
}

struct RecipientChip_Previews: PreviewProvider {
static var previews: some View {
RecipientChip(recipient: PreviewHelper.sampleRecipient1, fieldType: .to) {
/* Preview */
} switchFocusHandler: {
/* Preview */
}
}
}
103 changes: 103 additions & 0 deletions Mail/Components/RecipientChipLabel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
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 Foundation
import MailCore
import SwiftUI
import UIKit

struct RecipientChipLabelView: UIViewRepresentable {
let recipient: Recipient
let removeHandler: () -> Void
let switchFocusHandler: () -> Void

func makeUIView(context: Context) -> RecipientChipLabel {
let label = RecipientChipLabel(recipient: recipient)
label.removeHandler = removeHandler
label.switchFocusHandler = switchFocusHandler
return label
}

func updateUIView(_ uiLabel: RecipientChipLabel, context: Context) {
uiLabel.text = recipient.name.isEmpty ? recipient.email : recipient.name
}
}

class RecipientChipLabel: UILabel, UIKeyInput {
var removeHandler: (() -> Void)?
var switchFocusHandler: (() -> Void)?

override var intrinsicContentSize: CGSize {
var contentSize = super.intrinsicContentSize
contentSize.height += UIConstants.chipInsets.top + UIConstants.chipInsets.bottom
contentSize.width += UIConstants.chipInsets.left + UIConstants.chipInsets.right
return contentSize
}

override var canBecomeFirstResponder: Bool { return true }

var hasText = false

init(recipient: Recipient) {
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.masksToBounds = true
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: UIConstants.chipInsets))
}

override func becomeFirstResponder() -> Bool {
updateColors(isFirstResponder: true)
return super.becomeFirstResponder()
}

override func resignFirstResponder() -> Bool {
updateColors(isFirstResponder: false)
return super.resignFirstResponder()
}

func insertText(_ text: String) {
if text == "\t" {
switchFocusHandler?()
}
}

func deleteBackward() {
removeHandler?()
}

private func updateColors(isFirstResponder: Bool) {
textColor = isFirstResponder ? UserDefaults.shared.accentColor.secondary.color : .tintColor
backgroundColor = isFirstResponder ? .tintColor : UserDefaults.shared.accentColor.secondary.color
}
}
72 changes: 38 additions & 34 deletions Mail/Components/RecipientField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,14 @@ import RealmSwift
import SwiftUI
import WrappingHStack

struct RecipientChip: View {
let recipient: Recipient
let removeButtonTapped: () -> Void

@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor

var body: some View {
Button(action: removeButtonTapped) {
Text(recipient.name.isEmpty ? recipient.email : recipient.name)
.textStyle(.bodyAccent)
.padding(.vertical, 6)
.lineLimit(1)
}
.padding(.leading, 12)
.padding(.trailing, 12)
.background(Capsule().fill(accentColor.secondary.swiftUIColor))
}
}

struct RecipientField: View {
@Binding var recipients: RealmSwift.List<Recipient>
@Binding var autocompletion: [Recipient]
@Binding var unknownRecipientAutocompletion: String
@Binding var addRecipientHandler: ((Recipient) -> Void)?

@FocusState var focusedField: ComposeViewFieldType?

let type: ComposeViewFieldType

@State private var currentText = ""
Expand All @@ -59,26 +42,18 @@ struct RecipientField: View {
VStack {
if !recipients.isEmpty {
WrappingHStack(recipients.indices, spacing: .constant(8), lineSpacing: 8) { i in
RecipientChip(recipient: recipients[i]) {
RecipientChip(recipient: recipients[i], fieldType: type, focusedField: _focusedField) {
remove(recipientAt: i)
} switchFocusHandler: {
switchFocus()
}
.focused($focusedField, equals: .chip(type.hashValue, recipients[i]))
}
.alignmentGuide(.newMessageCellAlignment) { d in d[.top] + 21 }
}
TextField("", text: $currentText)
.textContentType(.emailAddress)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.disableAutocorrection(true)
.multilineTextAlignment(.leading)

RecipientsTextFieldView(text: $currentText, onSubmit: submitTextField, onBackspace: handleBackspaceTextField)
.focused($focusedField, equals: type)
.onSubmit {
guard let recipient = autocompletion.first else { return }
add(recipient: recipient)
focusedField = type
@InjectService var matomo: MatomoUtils
matomo.track(eventWithCategory: .newMessage, action: .input, name: "addNewRecipient")
}
}
.onChange(of: currentText) { _ in
updateAutocompletion()
Expand All @@ -95,12 +70,31 @@ struct RecipientField: View {
}
}

@MainActor private func submitTextField() {
guard let recipient = autocompletion.first else {
IKSnackBar.showSnackBar(
message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail,
anchor: keyboardHeight
)
return
}
add(recipient: recipient)
@InjectService var matomo: MatomoUtils
matomo.track(eventWithCategory: .newMessage, action: .input, name: "addNewRecipient")
}

private func handleBackspaceTextField(isTextEmpty: Bool) {
if let recipient = recipients.last, isTextEmpty {
focusedField = .chip(type.hashValue, recipient)
}
}

private func updateAutocompletion() {
let trimmedCurrentText = currentText.trimmingCharacters(in: .whitespacesAndNewlines)

let contactManager = AccountManager.instance.currentContactManager
let autocompleteContacts = contactManager?.contacts(matching: trimmedCurrentText) ?? []
var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) }
let autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) }

withAnimation {
autocompletion = autocompleteRecipients.filter { !recipients.map(\.email).contains($0.email) }
Expand Down Expand Up @@ -133,6 +127,16 @@ struct RecipientField: View {
$recipients.remove(at: recipientAt)
}
}

private func switchFocus() {
guard case let .chip(hash, recipient) = focusedField else { return }

if recipient == recipients.last {
focusedField = type
} else if let recipientIndex = recipients.firstIndex(of: recipient) {
focusedField = .chip(hash, recipients[recipientIndex + 1])
}
}
}

struct RecipientField_Previews: PreviewProvider {
Expand Down
2 changes: 1 addition & 1 deletion Mail/Views/New Message/AutocompletionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct AutocompletionView: View {
Button {
onSelect(recipient)
} label: {
RecipientAutocompletionCell(recipient: recipient)
RecipientCell(recipient: recipient)
}
.padding(.horizontal, 8)

Expand Down
Loading

0 comments on commit 44b1719

Please sign in to comment.