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: Update ComposeMessageView #814

Merged
merged 41 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7d83888
feat: Create and present ComposeView v2
valentinperignon Jun 13, 2023
6661563
feat: Display toolbar with buttons and title
valentinperignon Jun 13, 2023
207e524
feat: Divide in several files
valentinperignon Jun 13, 2023
bf68134
feat: Create different cells
valentinperignon Jun 14, 2023
8f001fd
feat: Fold cells
valentinperignon Jun 14, 2023
09643ac
feat: Create view with draft
valentinperignon Jun 14, 2023
a6640a2
feat: Create body and init
valentinperignon Jun 14, 2023
461a0f7
feat: Add message editor
valentinperignon Jun 14, 2023
0544c20
feat: Import attachments
valentinperignon Jun 14, 2023
de6d6e0
feat: Move some computed properties to V2
valentinperignon Jun 14, 2023
56c25e3
feat: Send or dismiss draft
valentinperignon Jun 14, 2023
c4a38dd
feat: Prepare body
valentinperignon Jun 14, 2023
eb5865d
feat: Sync draft on disappear
valentinperignon Jun 14, 2023
48439a6
fix: Correct UITextField size
valentinperignon Jun 15, 2023
f8862e1
refactor: Create new views and handle focus
valentinperignon Jun 15, 2023
14852fe
feat: Handle autocompletion visibilty
valentinperignon Jun 15, 2023
ac5b554
feat: Update autocompletion
valentinperignon Jun 15, 2023
5138a8a
fix: Update views properties
valentinperignon Jun 16, 2023
f0e9f23
feat: Add custom recipient
valentinperignon Jun 16, 2023
5ff2a59
feat: Highlight recipient string
valentinperignon Jun 16, 2023
b7a59e1
feat: Create all ComposeView inits
valentinperignon Jun 16, 2023
175acfc
feat: Update recipients states
valentinperignon Jun 16, 2023
f10d331
refactor: Clean ComposeMessageV2 code
valentinperignon Jun 16, 2023
c86fe14
feat: Display loading overlay
valentinperignon Jun 16, 2023
1ebd518
feat: Get focus when touch field
valentinperignon Jun 19, 2023
15b5ca8
refactor: Remove old ComposeMessageView
valentinperignon Jun 19, 2023
0c3b4d8
refactor: Remove old compenents
valentinperignon Jun 19, 2023
6657253
refactor: Remove V2 for main view
valentinperignon Jun 19, 2023
175f6b5
refactor: Rename all views
valentinperignon Jun 19, 2023
49e36da
refactor: Clean code
valentinperignon Jun 19, 2023
adf203c
fix: Update some wrong behaviors
valentinperignon Jun 19, 2023
1825827
feat: Update autocompletion cells
valentinperignon Jun 19, 2023
f471691
feat: Add unknown recipient title
valentinperignon Jun 19, 2023
d0c8509
feat: Check if email is already used
valentinperignon Jun 19, 2023
c8e2dfe
fix: Already append unknown recipient
valentinperignon Jun 19, 2023
e92070d
chore: Update localization
valentinperignon Jun 19, 2023
6882056
fix: Improve UI
valentinperignon Jun 20, 2023
d8df5d4
fix: Use newMessageCellAlignment
valentinperignon Jun 20, 2023
3dab97a
feat: Add debounce
valentinperignon Jun 20, 2023
5292452
feat(ComposeMessageView): Rename extension file
valentinperignon Jun 20, 2023
51b6156
refactor: Rename observable object for debounce
valentinperignon Jun 20, 2023
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
38 changes: 31 additions & 7 deletions Mail/Components/RecipientCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,54 @@
import MailCore
import SwiftUI

struct RecipientCellModifier: ViewModifier {
func body(content: Content) -> some View {
content
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
}
}

extension View {
func recipientCellModifier() -> some View {
modifier(RecipientCellModifier())
}
}

struct RecipientCell: View {
let recipient: Recipient
var highlight: String?

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

if recipient.name.isEmpty {
Text(recipient.email)
Text(highlightedAttributedString(from: recipient.email))
.textStyle(.bodyMedium)
} else {
VStack(alignment: .leading) {
Text(recipient.name)
Text(highlightedAttributedString(from: recipient.name))
.textStyle(.bodyMedium)
Text(recipient.email)
Text(highlightedAttributedString(from: recipient.email))
.textStyle(.bodySecondary)
}
}
}
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
.recipientCellModifier()
}

private func highlightedAttributedString(from data: String) -> AttributedString {
var attributedString = AttributedString(data)
guard let highlight else { return attributedString }

if let range = attributedString.range(of: highlight, options: .caseInsensitive) {
attributedString[range].foregroundColor = .accentColor
}
return attributedString
}
}

Expand Down
47 changes: 47 additions & 0 deletions Mail/Components/UnknownRecipientView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
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 SwiftUI

struct UnknownRecipientView: View {
let size: CGFloat

private var iconSize: CGFloat {
return size - 2 * UIConstants.unknownRecipientHorizontalPadding
}

var body: some View {
Circle()
.fill(Color.accentColor)
.frame(width: size, height: size)
.overlay {
MailResourcesAsset.userBold.swiftUIImage
.resizable()
.foregroundColor(MailResourcesAsset.backgroundColor.swiftUIColor)
.frame(width: iconSize, height: iconSize)
}
}
}

struct UnknownRecipientView_Previews: PreviewProvider {
static var previews: some View {
UnknownRecipientView(size: 40)
}
}
12 changes: 6 additions & 6 deletions Mail/Helpers/RichTextEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import WebKit
struct RichTextEditor: UIViewRepresentable {
typealias UIViewType = MailEditorView

@Binding var model: RichTextEditorModel
@ObservedObject var model: RichTextEditorModel
@Binding var body: String
@Binding var isShowingCamera: Bool
@Binding var isShowingFileSelection: Bool
Expand All @@ -37,12 +37,12 @@ struct RichTextEditor: UIViewRepresentable {
let blockRemoteContent: Bool
var alert: ObservedObject<NewMessageAlert>.Wrapper

init(model: Binding<RichTextEditorModel>, body: Binding<String>,
init(model: RichTextEditorModel, body: Binding<String>,
alert: ObservedObject<NewMessageAlert>.Wrapper,
isShowingCamera: Binding<Bool>, isShowingFileSelection: Binding<Bool>, isShowingPhotoLibrary: Binding<Bool>,
becomeFirstResponder: Binding<Bool>,
blockRemoteContent: Bool) {
_model = model
_model = ObservedObject(wrappedValue: model)
_body = body
self.alert = alert
_isShowingCamera = isShowingCamera
Expand Down Expand Up @@ -146,9 +146,9 @@ extension SQTextEditorView {
}
}

struct RichTextEditorModel {
var cursorPosition: CGFloat = 0
var height: CGFloat = 0
class RichTextEditorModel: ObservableObject {
@Published var cursorPosition: CGFloat = 0
@Published var height: CGFloat = 0
}

class MailEditorView: SQTextEditorView {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ struct AttachmentsHeaderView: View {
}
}
.padding(.vertical, 1)
.padding(.horizontal, 16)
}
.padding(.horizontal, 16)
}
}
.customAlert(item: $attachmentsManager.globalError) { error in
Expand Down
63 changes: 63 additions & 0 deletions Mail/Views/New Message/AutocompletionCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
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 SwiftUI

struct AutocompletionCell: View {
let addRecipient: @MainActor (Recipient) -> Void
let recipient: Recipient
var highlight: String?
let alreadyAppend: Bool
let unknownRecipient: Bool

var body: some View {
HStack(spacing: 12) {
Button {
addRecipient(recipient)
} label: {
if unknownRecipient {
UnknownRecipientCell(recipient: recipient)
} else {
RecipientCell(recipient: recipient, highlight: highlight)
}
}
.allowsHitTesting(!alreadyAppend || unknownRecipient)
.opacity(alreadyAppend && !unknownRecipient ? 0.5 : 1)

if alreadyAppend && !unknownRecipient {
MailResourcesAsset.checked.swiftUIImage
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(MailResourcesAsset.textTertiaryColor.swiftUIColor)
}
}
}
}

struct AutocompletionCell_Previews: PreviewProvider {
static var previews: some View {
AutocompletionCell(
addRecipient: { _ in /* Preview */ },
recipient: PreviewHelper.sampleRecipient1,
alreadyAppend: false,
unknownRecipient: false
)
}
}
82 changes: 58 additions & 24 deletions Mail/Views/New Message/AutocompletionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,50 +16,84 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import Combine
import MailCore
import MailResources
import RealmSwift
import SwiftUI

struct AutocompletionView: View {
@State private var shouldAddUserProposal = false

@ObservedObject var textDebounce: TextDebounce

@Binding var autocompletion: [Recipient]
@Binding var unknownRecipientAutocompletion: String
@Binding var addedRecipients: RealmSwift.List<Recipient>

let onSelect: (Recipient) -> Void
let addRecipient: @MainActor (Recipient) -> Void

var body: some View {
LazyVStack {
LazyVStack(spacing: UIConstants.autocompletionVerticalPadding) {
ForEach(autocompletion) { recipient in
VStack(alignment: .leading, spacing: 8) {
Button {
onSelect(recipient)
} label: {
RecipientCell(recipient: recipient)
}
.padding(.horizontal, 8)
let isLastRecipient = autocompletion.last?.isSameRecipient(as: recipient) == true
let isUserProposal = shouldAddUserProposal && isLastRecipient

IKDivider()
VStack(alignment: .leading, spacing: UIConstants.autocompletionVerticalPadding) {
AutocompletionCell(
addRecipient: addRecipient,
recipient: recipient,
highlight: textDebounce.text,
alreadyAppend: addedRecipients.contains { $0.isSameRecipient(as: recipient) },
unknownRecipient: isUserProposal
)

if !isLastRecipient {
IKDivider()
}
}
}
}
.onAppear {
updateAutocompletion(textDebounce.text)
}
.onReceive(textDebounce.$text.debounce(for: .milliseconds(150), scheduler: DispatchQueue.main)) { currentValue in
updateAutocompletion("\(currentValue)")
}
}

if !unknownRecipientAutocompletion.isEmpty {
Button {
onSelect(Recipient(email: unknownRecipientAutocompletion, name: ""))
} label: {
AddRecipientCell(recipientEmail: unknownRecipientAutocompletion)
}
.padding(.horizontal, 8)
private func updateAutocompletion(_ search: String) {
guard let contactManager = AccountManager.instance.currentContactManager else {
withAnimation {
autocompletion = []
}
return
}

let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines)

let autocompleteContacts = contactManager.contacts(matching: trimmedSearch)
var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) }

let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) }

withAnimation {
shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == textDebounce.text)
if shouldAddUserProposal {
autocompleteRecipients
.append(Recipient(email: textDebounce.text, name: ""))
}

autocompletion = autocompleteRecipients
}
.padding(.top, 8)
.padding(.horizontal, 8)
}
}

struct AutocompletionView_Previews: PreviewProvider {
static var previews: some View {
AutocompletionView(autocompletion: .constant([
PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3
]),
unknownRecipientAutocompletion: .constant("")) { _ in /* Preview */ }
AutocompletionView(
textDebounce: TextDebounce(),
autocompletion: .constant([]),
addedRecipients: .constant([PreviewHelper.sampleRecipient1].toRealmList())
) { _ in /* Preview */ }
}
}
Loading