diff --git a/Mail/Components/BottomBarView.swift b/Mail/Components/BottomBarView.swift
new file mode 100644
index 000000000..cedfe3958
--- /dev/null
+++ b/Mail/Components/BottomBarView.swift
@@ -0,0 +1,84 @@
+/*
+ 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 .
+ */
+
+import MailCore
+import MailResources
+import SwiftUI
+
+struct BottomBar: ViewModifier {
+ let isVisible: Bool
+ @ViewBuilder var items: () -> Items
+
+ func body(content: Content) -> some View {
+ content
+ .safeAreaInset(edge: .bottom) {
+ if isVisible {
+ BottomBarView(items: items)
+ .transition(.opacity)
+ }
+ }
+ }
+}
+
+extension View {
+ func bottomBar(isVisible: Bool = true, @ViewBuilder items: @escaping () -> Items) -> some View {
+ modifier(BottomBar(isVisible: isVisible, items: items))
+ }
+}
+
+struct BottomBarView: View {
+ @State private var hasBottomSafeArea = true
+
+ @ViewBuilder var items: () -> Items
+
+ var body: some View {
+ HStack {
+ Spacer(minLength: UIConstants.bottomBarHorizontalMinimumSpace)
+ items()
+ Spacer(minLength: UIConstants.bottomBarHorizontalMinimumSpace)
+ }
+ .padding(.top, UIConstants.bottomBarVerticalPadding)
+ .padding(.bottom, hasBottomSafeArea ? UIConstants.bottomBarSmallVerticalPadding : UIConstants.bottomBarVerticalPadding)
+ .background(MailResourcesAsset.backgroundTabBarColor.swiftUIColor)
+ .overlay(alignment: .top) {
+ Divider()
+ .frame(height: 1)
+ .overlay(Color(uiColor: .systemGray3))
+ }
+ .overlay {
+ ViewGeometry(key: BottomSafeAreaKey.self, property: \.safeAreaInsets.bottom)
+ }
+ .onPreferenceChange(BottomSafeAreaKey.self) { value in
+ hasBottomSafeArea = value > 0
+ }
+ }
+}
+
+struct BottomBarView_Previews: PreviewProvider {
+ static var previews: some View {
+ NavigationView {
+ List {
+ Text("View #1")
+ }
+ .navigationTitle("Title")
+ .bottomBar {
+ Text("Coucou")
+ }
+ }
+ }
+}
diff --git a/Mail/Components/MailButton.swift b/Mail/Components/MailButton.swift
index 31d9ed3ec..96db5370b 100644
--- a/Mail/Components/MailButton.swift
+++ b/Mail/Components/MailButton.swift
@@ -20,7 +20,7 @@ import MailCore
import MailResources
import SwiftUI
-// MARK: - Modifiers
+// MARK: - Environment
struct MailButtonStyleKey: EnvironmentKey {
static var defaultValue = MailButton.Style.large
diff --git a/Mail/Components/ToolbarButton.swift b/Mail/Components/ToolbarButton.swift
index ac4810be2..dd8ecfba9 100644
--- a/Mail/Components/ToolbarButton.swift
+++ b/Mail/Components/ToolbarButton.swift
@@ -30,8 +30,12 @@ struct ToolbarButtonLabel: View {
Label {
Text(text)
.textStyle(MailTextStyle.labelMediumAccent)
+ .minimumScaleFactor(0.8)
+ .lineLimit(1)
} icon: {
icon
+ .resizable()
+ .frame(width: 24, height: 24)
}
.dynamicLabelStyle(sizeClass: sizeClass ?? .regular)
}
diff --git a/Mail/Utils/GeometryReaderHelpers.swift b/Mail/Utils/GeometryReaderHelpers.swift
new file mode 100644
index 000000000..c8f677d3f
--- /dev/null
+++ b/Mail/Utils/GeometryReaderHelpers.swift
@@ -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 .
+ */
+
+import SwiftUI
+
+struct ViewWidthKey: PreferenceKey {
+ static var defaultValue: CGFloat = .zero
+
+ static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
+ value = max(value, nextValue())
+ }
+}
+
+struct BottomSafeAreaKey: PreferenceKey {
+ static var defaultValue: CGFloat = .zero
+
+ static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
+ value = nextValue()
+ }
+}
+
+struct ViewGeometry: View where K: PreferenceKey {
+ let key: K.Type
+ let property: KeyPath
+
+ var body: some View {
+ GeometryReader { proxy in
+ Color.clear
+ .preference(key: key, value: proxy[keyPath: property])
+ }
+ }
+}
diff --git a/Mail/Views/Thread List/ThreadListModifiers.swift b/Mail/Views/Thread List/ThreadListModifiers.swift
index 6445d3b5a..d5463219e 100644
--- a/Mail/Views/Thread List/ThreadListModifiers.swift
+++ b/Mail/Views/Thread List/ThreadListModifiers.swift
@@ -130,37 +130,34 @@ struct ThreadListToolbar: ViewModifier {
.accessibilityLabel(MailResourcesStrings.Localizable.contentDescriptionUserAvatar)
}
}
-
- ToolbarItemGroup(placement: .bottomBar) {
- if multipleSelectionViewModel.isEnabled {
- HStack(spacing: 0) {
- ForEach(multipleSelectionViewModel.toolbarActions) { action in
- ToolbarButton(
- text: action.shortTitle ?? action.title,
- icon: action.icon
- ) {
- Task {
- await tryOrDisplayError {
- try await multipleSelectionViewModel.didTap(
- action: action,
- flushAlert: $flushAlert
- )
- }
- }
+ }
+ .bottomBar(isVisible: multipleSelectionViewModel.isEnabled) {
+ HStack(spacing: 0) {
+ ForEach(multipleSelectionViewModel.toolbarActions) { action in
+ ToolbarButton(
+ text: action.shortTitle ?? action.title,
+ icon: action.icon
+ ) {
+ Task {
+ await tryOrDisplayError {
+ try await multipleSelectionViewModel.didTap(
+ action: action,
+ flushAlert: $flushAlert
+ )
}
- .disabled(action == .archive && splitViewManager.selectedFolder?.role == .archive)
- }
-
- ToolbarButton(
- text: MailResourcesStrings.Localizable.buttonMore,
- icon: MailResourcesAsset.plusActions.swiftUIImage
- ) {
- multipleSelectionActionsTarget = .threads(Array(multipleSelectionViewModel.selectedItems), true)
}
}
- .disabled(multipleSelectionViewModel.selectedItems.isEmpty)
+ .disabled(action == .archive && splitViewManager.selectedFolder?.role == .archive)
+ }
+
+ ToolbarButton(
+ text: MailResourcesStrings.Localizable.buttonMore,
+ icon: MailResourcesAsset.plusActions.swiftUIImage
+ ) {
+ multipleSelectionActionsTarget = .threads(Array(multipleSelectionViewModel.selectedItems), true)
}
}
+ .disabled(multipleSelectionViewModel.selectedItems.isEmpty)
}
.actionsPanel(actionsTarget: $multipleSelectionActionsTarget) {
withAnimation {
diff --git a/Mail/Views/Thread/MessageHeaderDetailView.swift b/Mail/Views/Thread/MessageHeaderDetailView.swift
index 58a26abdd..f8d774d21 100644
--- a/Mail/Views/Thread/MessageHeaderDetailView.swift
+++ b/Mail/Views/Thread/MessageHeaderDetailView.swift
@@ -25,23 +25,6 @@ import RealmSwift
import SwiftUI
import WrappingHStack
-struct ViewWidthKey: PreferenceKey {
- static var defaultValue: CGFloat = .zero
-
- static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
- value = max(value, nextValue())
- }
-}
-
-struct ViewGeometry: View {
- var body: some View {
- GeometryReader { geometry in
- Color.clear
- .preference(key: ViewWidthKey.self, value: geometry.size.width)
- }
- }
-}
-
struct MessageHeaderDetailView: View {
@ObservedRealmObject var message: Message
@@ -109,7 +92,7 @@ struct RecipientLabel: View {
HStack(alignment: .top) {
Text(title)
.textStyle(.bodySmallSecondary)
- .background(ViewGeometry())
+ .background(ViewGeometry(key: ViewWidthKey.self, property: \.size.width))
.frame(width: labelWidth, alignment: .leading)
VStack(alignment: .leading, spacing: 4) {
ForEach(recipients, id: \.self) { recipient in
diff --git a/Mail/Views/Thread/ThreadView.swift b/Mail/Views/Thread/ThreadView.swift
index 6cf0c2a5d..b4a490046 100644
--- a/Mail/Views/Thread/ThreadView.swift
+++ b/Mail/Views/Thread/ThreadView.swift
@@ -53,10 +53,10 @@ struct ThreadView: View {
var body: some View {
ScrollView {
VStack(spacing: 0) {
- GeometryReader { geometry in
+ GeometryReader { proxy in
Color.clear.preference(
key: ScrollOffsetPreferenceKey.self,
- value: geometry.frame(in: .named("scrollView")).origin
+ value: proxy.frame(in: .named("scrollView")).origin
)
}
.frame(width: 0, height: 0)
@@ -100,32 +100,33 @@ struct ThreadView: View {
.foregroundColor(thread.flagged ? MailResourcesAsset.yellowColor.swiftUIColor : .accentColor)
}
}
- ToolbarItemGroup(placement: .bottomBar) {
- ForEach(toolbarActions) { action in
- if action == .reply {
- ToolbarButton(text: action.title, icon: action.icon) {
- didTap(action: action)
- }
- .adaptivePanel(item: $replyOrReplyAllMessage) { message in
- ReplyActionsView(
- mailboxManager: mailboxManager,
- message: message,
- messageReply: $navigationStore.messageReply
- )
- }
- } else {
- ToolbarButton(text: action.title, icon: action.icon) {
- didTap(action: action)
- }
- .disabled(action == .archive && thread.folder?.role == .archive)
+ }
+ .bottomBar {
+ ForEach(toolbarActions) { action in
+ if action == .reply {
+ ToolbarButton(text: action.title, icon: action.icon) {
+ didTap(action: action)
}
- Spacer()
- }
- ActionsPanelButton(threads: [thread]) {
- ToolbarButtonLabel(text: MailResourcesStrings.Localizable.buttonMore,
- icon: MailResourcesAsset.plusActions.swiftUIImage)
+ .adaptivePanel(item: $replyOrReplyAllMessage) { message in
+ ReplyActionsView(
+ mailboxManager: mailboxManager,
+ message: message,
+ messageReply: $navigationStore.messageReply
+ )
+ }
+ } else {
+ ToolbarButton(text: action.title, icon: action.icon) {
+ didTap(action: action)
+ }
+ .disabled(action == .archive && thread.folder?.role == .archive)
}
+ Spacer()
+ }
+ ActionsPanelButton(threads: [thread]) {
+ ToolbarButtonLabel(text: MailResourcesStrings.Localizable.buttonMore,
+ icon: MailResourcesAsset.plusActions.swiftUIImage)
}
+ .frame(maxWidth: .infinity)
}
.onChange(of: thread.messages) { newMessagesList in
if newMessagesList.isEmpty || thread.messageInFolderCount == 0 {
diff --git a/MailCore/UI/UIConstants.swift b/MailCore/UI/UIConstants.swift
index 3a7b9540d..3c5fb0a56 100644
--- a/MailCore/UI/UIConstants.swift
+++ b/MailCore/UI/UIConstants.swift
@@ -101,6 +101,10 @@ public enum UIConstants {
public static let buttonsRadius: CGFloat = 16
public static let buttonsIconSize: CGFloat = 16
+ public static let bottomBarVerticalPadding: CGFloat = 8
+ public static let bottomBarSmallVerticalPadding: CGFloat = 4
+ public static let bottomBarHorizontalMinimumSpace: CGFloat = 8
+
public static let bottomSheetHorizontalPadding: CGFloat = 24
public static let componentsMaxWidth: CGFloat = 496