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

fix: Current toolbar break when the screen is too small #771

Merged
merged 12 commits into from
Jun 7, 2023
84 changes: 84 additions & 0 deletions Mail/Components/BottomBarView.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

import MailCore
import MailResources
import SwiftUI

struct BottomBar<Items: View>: 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<Items: View>(isVisible: Bool = true, @ViewBuilder items: @escaping () -> Items) -> some View {
modifier(BottomBar(isVisible: isVisible, items: items))
}
}

struct BottomBarView<Items: View>: 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")
}
}
}
}
2 changes: 1 addition & 1 deletion Mail/Components/MailButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import MailCore
import MailResources
import SwiftUI

// MARK: - Modifiers
// MARK: - Environment

struct MailButtonStyleKey: EnvironmentKey {
static var defaultValue = MailButton.Style.large
Expand Down
4 changes: 4 additions & 0 deletions Mail/Components/ToolbarButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
47 changes: 47 additions & 0 deletions Mail/Utils/GeometryReaderHelpers.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 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<K>: View where K: PreferenceKey {
let key: K.Type
let property: KeyPath<GeometryProxy, K.Value>

var body: some View {
GeometryReader { proxy in
Color.clear
.preference(key: key, value: proxy[keyPath: property])
}
}
}
49 changes: 23 additions & 26 deletions Mail/Views/Thread List/ThreadListModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 1 addition & 18 deletions Mail/Views/Thread/MessageHeaderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
51 changes: 26 additions & 25 deletions Mail/Views/Thread/ThreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions MailCore/UI/UIConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down