Skip to content

Commit

Permalink
Merge pull request #764 from Infomaniak/improve-multipleselection-ani…
Browse files Browse the repository at this point in the history
…mation

fix(MultipleSelection): Improve animations
  • Loading branch information
valentinperignon committed Jun 1, 2023
2 parents b927a5c + 57ebe62 commit a52ec82
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 40 deletions.
4 changes: 3 additions & 1 deletion Mail/Components/CheckboxView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import SwiftUI

struct CheckboxView: View {
@AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor

let isSelected: Bool
let size: CGFloat

Expand All @@ -33,14 +34,15 @@ struct CheckboxView: View {
var body: some View {
ZStack {
Circle()
.strokeBorder(Color.accentColor, lineWidth: 2)
.strokeBorder(Color.accentColor, lineWidth: isSelected ? 0 : 2)
.background(Circle().fill(isSelected ? Color.accentColor : Color.clear))
.frame(width: size, height: size)
MailResourcesAsset.check.swiftUIImage
.foregroundColor(accentColor.onAccent.swiftUIColor)
.frame(height: UIConstants.checkmarkSize)
.opacity(isSelected ? 1 : 0)
}
.animation(nil, value: isSelected)
}
}

Expand Down
2 changes: 2 additions & 0 deletions Mail/Components/SelectionBackground.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct SelectionBackground: View {

let selectionType: SelectionBackgroundKind
var paddingLeading: CGFloat = 8
var withAnimation = true

var body: some View {
Rectangle()
Expand All @@ -68,6 +69,7 @@ struct SelectionBackground: View {
.padding(.leading, paddingLeading)
.padding(.vertical, selectionType.verticalPadding)
.opacity(selectionType.opacity)
.animation(withAnimation ? .default : nil, value: selectionType.opacity)
}
}

Expand Down
49 changes: 25 additions & 24 deletions Mail/Components/ThreadCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,8 @@ import MailResources
import SwiftUI

extension Animation {
static func threadListCheckbox(isMultipleSelectionEnabled isEnabled: Bool) -> Animation {
.default.delay(isEnabled ? 0.5 : 0)
}

static func threadListSlide(density: ThreadDensity, isMultipleSelectionEnabled isEnabled: Bool) -> Animation {
if density == .large {
return .default
}
return .default.speed(2).delay(isEnabled ? 0 : 0.22)
static var threadListSlide: Animation {
.default.speed(2)
}
}

Expand Down Expand Up @@ -81,6 +74,10 @@ struct ThreadCellDataHolder {

struct ThreadCell: View {
@EnvironmentObject private var mailboxManager: MailboxManager

/// With normal or compact density, the checkbox should appear and disappear at different times of the cell offset.
@State private var shouldDisplayCheckbox = false

let thread: Thread

let dataHolder: ThreadCellDataHolder
Expand All @@ -104,12 +101,7 @@ struct ThreadCell: View {
return label
}

init(
thread: Thread,
density: ThreadDensity,
isMultipleSelectionEnabled: Bool = false,
isSelected: Bool = false
) {
init(thread: Thread, density: ThreadDensity, isMultipleSelectionEnabled: Bool = false, isSelected: Bool = false) {
self.thread = thread

dataHolder = ThreadCellDataHolder(thread: thread)
Expand All @@ -124,10 +116,6 @@ struct ThreadCell: View {
var body: some View {
HStack(spacing: 8) {
UnreadIndicatorView(hidden: !thread.hasUnseenMessages)
.animation(
.threadListSlide(density: density, isMultipleSelectionEnabled: isMultipleSelectionEnabled),
value: isMultipleSelectionEnabled
)
.accessibilityLabel(additionalAccessibilityLabel)
.accessibilityHidden(additionalAccessibilityLabel.isEmpty)

Expand All @@ -137,14 +125,13 @@ struct ThreadCell: View {
AvatarView(avatarDisplayable: recipient, size: 40)
CheckboxView(isSelected: isSelected, density: density)
.opacity(isSelected ? 1 : 0)
.animation(nil, value: isSelected)
}
.accessibility(hidden: true)
} else if isMultipleSelectionEnabled {
CheckboxView(isSelected: isSelected, density: density)
.animation(
.threadListCheckbox(isMultipleSelectionEnabled: isMultipleSelectionEnabled),
value: isMultipleSelectionEnabled
)
.opacity(shouldDisplayCheckbox ? 1 : 0)
.animation(.default.speed(1.5), value: shouldDisplayCheckbox)
}
}
.padding(.trailing, 4)
Expand All @@ -159,7 +146,7 @@ struct ThreadCell: View {
}
}
.animation(
.threadListSlide(density: density, isMultipleSelectionEnabled: isMultipleSelectionEnabled),
isMultipleSelectionEnabled ? .threadListSlide : .threadListSlide.delay(UIConstants.checkboxDisappearOffsetDelay),
value: isMultipleSelectionEnabled
)
}
Expand All @@ -168,6 +155,20 @@ struct ThreadCell: View {
.padding(.vertical, density.cellVerticalPadding)
.clipped()
.accessibilityElement(children: .combine)
.onChange(of: isMultipleSelectionEnabled) { isEnabled in
guard density != .large else { return }

withAnimation {
if isEnabled {
// We should wait a bit before showing the checkbox
DispatchQueue.main.asyncAfter(deadline: .now() + UIConstants.checkboxAppearDelay) {
self.shouldDisplayCheckbox = true
}
} else {
self.shouldDisplayCheckbox = false
}
}
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion Mail/Utils/FloatingActionButtonModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ struct FloatingActionButtonModifier: ViewModifier {
}

extension View {
func floatingActionButton(isEnabled: Bool = true, icon: MailResourcesImages, title: String, action: @escaping () -> Void) -> some View {
func floatingActionButton(isEnabled: Bool = true, icon: MailResourcesImages, title: String,
action: @escaping () -> Void) -> some View {
modifier(FloatingActionButtonModifier(isEnabled: isEnabled, icon: icon, title: title, action: action))
}
}
21 changes: 11 additions & 10 deletions Mail/Views/Thread List/ThreadListCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ import SwiftUI
struct ThreadListCell: View {
@EnvironmentObject var splitViewManager: SplitViewManager

let thread: Thread

let viewModel: ThreadListViewModel
let multipleSelectionViewModel: ThreadListMultipleSelectionViewModel
@ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel

@Binding var editedMessageDraft: Draft?

let thread: Thread

let threadDensity: ThreadDensity

let isSelected: Bool
let isMultiSelected: Bool

@Binding var editedMessageDraft: Draft?

private var selectionType: SelectionBackgroundKind {
if multipleSelectionViewModel.isEnabled {
return isMultiSelected ? .multiple : .none
Expand All @@ -52,9 +52,10 @@ struct ThreadListCell: View {
isMultipleSelectionEnabled: multipleSelectionViewModel.isEnabled,
isSelected: isMultiSelected
)
.background(SelectionBackground(selectionType: selectionType, paddingLeading: 4))
.background(SelectionBackground(selectionType: selectionType, paddingLeading: 4, withAnimation: false))
.contentShape(Rectangle())
.onTapGesture { didTapCell() }
.onLongPressGesture(minimumDuration: 0.3) { didLongPressCell() }
.onLongPressGesture { didLongPressCell() }
.swipeActions(thread: thread, viewModel: viewModel, multipleSelectionViewModel: multipleSelectionViewModel)
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
.listRowSeparator(.hidden)
Expand Down Expand Up @@ -100,15 +101,15 @@ struct ThreadListCell: View {
struct ThreadListCell_Previews: PreviewProvider {
static var previews: some View {
ThreadListCell(
thread: PreviewHelper.sampleThread,
viewModel: ThreadListViewModel(mailboxManager: PreviewHelper.sampleMailboxManager,
folder: PreviewHelper.sampleFolder,
isCompact: false),
multipleSelectionViewModel: ThreadListMultipleSelectionViewModel(mailboxManager: PreviewHelper.sampleMailboxManager),
editedMessageDraft: .constant(nil),
thread: PreviewHelper.sampleThread,
threadDensity: .large,
isSelected: false,
isMultiSelected: false,
editedMessageDraft: .constant(nil)
isMultiSelected: false
)
}
}
8 changes: 4 additions & 4 deletions Mail/Views/Thread List/ThreadListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ struct ThreadListView: View {
ForEach(viewModel.sections) { section in
Section {
ForEach(section.threads) { thread in
ThreadListCell(thread: thread,
viewModel: viewModel,
ThreadListCell(viewModel: viewModel,
multipleSelectionViewModel: multipleSelectionViewModel,
editedMessageDraft: $editedMessageDraft,
thread: thread,
threadDensity: threadDensity,
isSelected: viewModel.selectedThread?.uid == thread.uid,
isMultiSelected: multipleSelectionViewModel.selectedItems
.contains { $0.id == thread.id },
editedMessageDraft: $editedMessageDraft)
.contains { $0.id == thread.id })
.id(thread.id)
}
} header: {
Expand Down
3 changes: 3 additions & 0 deletions MailCore/UI/UIConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ public enum UIConstants {
public static let checkmarkSize: CGFloat = 14
public static let checkboxLargeSize: CGFloat = 40

public static let checkboxAppearDelay = 0.2
public static let checkboxDisappearOffsetDelay = 0.35

public static let buttonsRadius: CGFloat = 16
public static let buttonsIconSize: CGFloat = 16

Expand Down

0 comments on commit a52ec82

Please sign in to comment.