Skip to content

feat(Snooze): Core feature #1745

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

Merged
merged 36 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0ed83e9
chore: Update resources (Strings + Icons)
valentinperignon Mar 24, 2025
3b64c5e
feat: Add formater
valentinperignon Mar 24, 2025
4a06a20
feat(MessageHeaderActionView): Set button style in environment
valentinperignon Mar 25, 2025
f14baa6
feat: Add SnoozedThreadHeaderView view
valentinperignon Mar 25, 2025
bbac645
feat: Display thread header if necessary
valentinperignon Mar 25, 2025
1d291a0
feat(MessageHeaderActionView): Set button style in environment
valentinperignon Mar 25, 2025
dad62df
fix(MessageHeaderActionView): Add `MessageHeaderDivider`
valentinperignon Mar 31, 2025
fb56502
feat: Hide actions is other folders
valentinperignon Apr 1, 2025
515198d
fix(Schedule): Use Binding instead of closure
valentinperignon Mar 26, 2025
e3baa5f
feat: Add ScheduleType enum
valentinperignon Mar 27, 2025
0b4e238
feat: CustomScheduleAlertView can be used for Snooze and Scheduled
valentinperignon Mar 28, 2025
9f48101
chore: Move file to a separate folder
valentinperignon Mar 28, 2025
5b4497f
feat: Floating panel can be used for both schedule and snooze
valentinperignon Mar 28, 2025
2029997
style: Clean code
valentinperignon Mar 28, 2025
1b98b0f
feat: Add SnoozedThreadHeaderView view
valentinperignon Mar 25, 2025
efe3ead
chore: Import resources
valentinperignon Mar 28, 2025
741af47
refactor: Rename `ScheduleOption`
valentinperignon Apr 4, 2025
712e0b7
feat: Display thread header if necessary
valentinperignon Mar 25, 2025
d504cee
feat: Add ScheduleType enum
valentinperignon Mar 27, 2025
668bc0e
feat: Show floating panel
valentinperignon Mar 28, 2025
2e1dc6c
feat: Add MailboxManager functions
valentinperignon Mar 31, 2025
6968d05
feat: Update and cancel message
valentinperignon Mar 31, 2025
0124ca7
feat: Snoozed thread can be updated or canceled
valentinperignon Mar 31, 2025
1e20681
feat: Add necessary resources
valentinperignon Apr 1, 2025
4a1119c
feat: Update actions lists
valentinperignon Apr 1, 2025
7cc9a7c
feat: Add plural strings
valentinperignon Apr 1, 2025
0d1cc8a
feat: Unsnooze message
valentinperignon Apr 1, 2025
43fb0e9
feat: Cancel snooze from header
valentinperignon Apr 1, 2025
6d17394
feat: Update snooze date from header
valentinperignon Apr 1, 2025
67f1401
chore: Update performSnooze function
valentinperignon Apr 1, 2025
3fb372d
feat: Messages can be snoozed from header and floating panel
valentinperignon Apr 2, 2025
4867b34
fix: Message can be snoozed in multiple selection
valentinperignon Apr 2, 2025
4a1c6cc
feat: Update behavior according to action
valentinperignon Apr 2, 2025
f1ae762
feat: Listen FeatureFlag
valentinperignon Apr 4, 2025
9842749
feat(Snooze): Snooze actions (#3) (#1736)
valentinperignon Apr 7, 2025
a3f0055
chore: Clean code
valentinperignon Apr 7, 2025
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
2 changes: 1 addition & 1 deletion Mail/Components/ActionsPanelButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct ActionsPanelButton<Content: View>: View {
panelSource: panelSource,
popoverArrowEdge: popoverArrowEdge
) { action in
if action == .markAsUnread {
if action == .markAsUnread || action == .snooze || action == .modifySnooze {
dismiss()
}
}
Expand Down
55 changes: 37 additions & 18 deletions Mail/Views/Alerts/CustomScheduleAlertView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,32 @@ import MailCoreUI
import MailResources
import SwiftUI

extension ScheduleType {
var alertErrorMessage: String {
let limit = Int(minimumInterval / 60)

switch self {
case .scheduledDraft:
return MailResourcesStrings.Localizable.errorScheduleDelayTooShort(limit)
case .snooze:
return MailResourcesStrings.Localizable.errorScheduledSnoozeDelayTooShort(limit)
}
}
}

struct CustomScheduleAlertView: View {
@LazyInjectService private var matomo: MatomoUtils

@State private var isShowingError = false
@State private var selectedDate: Date

let type: ScheduleType
let confirmAction: (Date) -> Void
let cancelAction: (() -> Void)?

private let maximumDelay = Date.now.addingTimeInterval(60 * 60 * 24 * 365 * 10) // 10 years

private var isDelayTooShort: Bool {
selectedDate < Date.minimumScheduleDelay
}

init(startingDate: Date, confirmAction: @escaping (Date) -> Void, cancelAction: (() -> Void)? = nil) {
_selectedDate = .init(initialValue: startingDate)
init(type: ScheduleType, date: Date?, confirmAction: @escaping (Date) -> Void, cancelAction: (() -> Void)? = nil) {
_selectedDate = .init(wrappedValue: date ?? type.minimumDate)
self.type = type
self.confirmAction = confirmAction
self.cancelAction = cancelAction
}
Expand All @@ -55,33 +64,43 @@ struct CustomScheduleAlertView: View {
DatePicker(
MailResourcesStrings.Localizable.datePickerTitle,
selection: $selectedDate,
in: Date.minimumScheduleDelay.addingTimeInterval(60) ... maximumDelay
in: type.minimumDate ... type.maximumDate
)
.labelsHidden()
.onChange(of: selectedDate) { newDate in
isShowingError = newDate < Date.minimumScheduleDelay || newDate > maximumDelay
isShowingError = !type.isDateInValidTimeframe(newDate)
}

Text(MailResourcesStrings.Localizable.errorScheduleDelayTooShortPlural(5))
Text(type.alertErrorMessage)
.textStyle(.labelError)
.padding(.top, value: .micro)
.opacity(isShowingError ? 1 : 0)
.padding(.bottom, value: .mini)

ModalButtonsView(primaryButtonTitle: MailResourcesStrings.Localizable.buttonScheduleTitle,
secondaryButtonTitle: MailResourcesStrings.Localizable.buttonCancel,
primaryButtonEnabled: !isShowingError,
primaryButtonAction: executeActionIfPossible,
secondaryButtonAction: cancelAction)
ModalButtonsView(
primaryButtonTitle: MailResourcesStrings.Localizable.buttonConfirm,
secondaryButtonTitle: MailResourcesStrings.Localizable.buttonCancel,
primaryButtonEnabled: !isShowingError,
primaryButtonAction: executeActionIfPossible,
secondaryButtonAction: cancelAction
)
}
}

private func executeActionIfPossible() throws {
guard !isDelayTooShort else {
guard type.isDateInValidTimeframe(selectedDate) else {
isShowingError = true
throw MailError.tooShortScheduleDelay
}

confirmAction(selectedDate)
matomo.track(eventWithCategory: .scheduleSend, name: "customSchedule")
UserDefaults.shared[keyPath: type.lastCustomScheduleDateKeyPath] = selectedDate
matomo.track(eventWithCategory: type.matomoCategory, name: "customSchedule")
}
}

#Preview {
CustomScheduleAlertView(type: .scheduledDraft, date: .now) { date in
print("Selected Date: \(date)")
}
}
16 changes: 16 additions & 0 deletions Mail/Views/Bottom sheets/Actions/ActionsPanelViewModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct ActionsPanelViewModifier: ViewModifier {
@ModalState private var messagesToMove: [Message]?
@ModalState private var flushAlert: FlushAlertState?
@ModalState private var shareMailLink: ShareMailLinkResult?
@ModalState private var messagesToSnooze: [Message]?
@ModalState private var messagesToDownload: [Message]?

@Binding var messages: [Message]?
Expand All @@ -73,10 +74,20 @@ struct ActionsPanelViewModifier: ViewModifier {
nearestReportedForPhishingMessagesAlert: $reportedForPhishingMessages,
nearestReportedForDisplayProblemMessageAlert: $reportedForDisplayProblemMessage,
nearestShareMailLinkPanel: $shareMailLink,
nearestMessagesToSnooze: $messagesToSnooze,
messagesToDownload: $messagesToDownload
)
}

private var initialSnoozedDate: Date? {
guard let messages,
let initialDate = messages.first?.snoozeEndDate,
messages.allSatisfy({ $0.isSnoozed && $0.snoozeEndDate == initialDate })
else { return nil }

return initialDate
}

func body(content: Content) -> some View {
content.adaptivePanel(item: $messages, popoverArrowEdge: popoverArrowEdge) { messages in
ActionsView(
Expand Down Expand Up @@ -136,5 +147,10 @@ struct ActionsPanelViewModifier: ViewModifier {
.backport.presentationDetents([.medium, .large])
}
}
.snoozedFloatingPanel(
messages: messagesToSnooze,
initialDate: initialSnoozedDate,
folder: originFolder?.freezeIfNeeded()
) { completionHandler?($0) }
}
}
24 changes: 17 additions & 7 deletions Mail/Views/New Message/ComposeMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ struct ComposeMessageView: View {
changeFolderAction: changeSelectedFolder
) {
mainViewState.isShowingMyKSuiteUpgrade = true
matomo.track(eventWithCategory: .myKSuiteUpgrade, name: "dailyLimitReachedUpgrade")
matomo.track(eventWithCategory: .myKSuiteUpgradeBottomSheet, name: "dailyLimitReachedUpgrade")
}
}
.customAlert(item: $isShowingAlert) { alert in
Expand Down Expand Up @@ -304,10 +304,9 @@ struct ComposeMessageView: View {
.matomoView(view: ["ComposeMessage"])
.scheduleFloatingPanel(
isPresented: $isShowingSchedulePanel,
draftSaveOption: $draft.action,
draftDate: $draft.scheduleDate,
mailboxManager: mailboxManager,
completionHandler: dismissMessageView
type: .scheduledDraft,
initialDate: draft.scheduleDate,
completionHandler: didScheduleDraft
)
}

Expand Down Expand Up @@ -338,6 +337,17 @@ struct ComposeMessageView: View {
}
}

private func didScheduleDraft(_ date: Date) {
if let liveDraft = draft.thaw() {
try? liveDraft.realm?.write {
liveDraft.scheduleDate = date
liveDraft.action = .schedule
}
}

dismissMessageView()
}

private func didTouchSend() {
guard !draft.subject.isEmpty else {
matomo.track(eventWithCategory: .newMessage, name: "sendWithoutSubject")
Expand All @@ -347,7 +357,7 @@ struct ComposeMessageView: View {

let mailbox = mailboxManager.mailbox
let mailboxIsFull = mailbox.quotas?.progression ?? 0 >= 1
if mailbox.isFree && mailbox.isLimited && mailboxIsFull {
if mailbox.isMyKSuiteFree && mailboxIsFull {
matomo.track(eventWithCategory: .newMessage, name: "trySendingWithMailboxFull")
Task {
if let liveDraft = draft.thaw() {
Expand All @@ -360,7 +370,7 @@ struct ComposeMessageView: View {
message: MailResourcesStrings.Localizable.myKSuiteSpaceFullAlert,
action: IKSnackBar.Action(title: MailResourcesStrings.Localizable.buttonUpgrade) {
mainViewState.isShowingMyKSuiteUpgrade = true
matomo.track(eventWithCategory: .myKSuiteUpgrade, name: "notEnoughStorageUpgrade")
matomo.track(eventWithCategory: .myKSuiteUpgradeBottomSheet, name: "notEnoughStorageUpgrade")
}
)
return
Expand Down
100 changes: 0 additions & 100 deletions Mail/Views/New Message/Scheduled panel/ScheduleFloatingPanel.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import DesignSystem
import InfomaniakCoreCommonUI
import InfomaniakDI
import MailCore
import MailCoreUI
Expand All @@ -25,12 +26,15 @@ import MyKSuite
import SwiftUI

struct CustomScheduleButton: View {
@LazyInjectService private var matomo: MatomoUtils

@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var mailboxManager: MailboxManager

@Binding var customSchedule: Bool
@Binding var isShowingCustomScheduleAlert: Bool
@Binding var isShowingMyKSuiteUpgrade: Bool

let isMyKSuiteStandard: Bool
let type: ScheduleType

var body: some View {
Button(action: showCustomSchedulePicker) {
Expand All @@ -42,7 +46,7 @@ struct CustomScheduleButton: View {
.textStyle(.body)
.frame(maxWidth: .infinity, alignment: .leading)

if isMyKSuiteStandard {
if mailboxManager.mailbox.isMyKSuiteFree {
MyKSuitePlusChip()
}

Expand All @@ -53,11 +57,24 @@ struct CustomScheduleButton: View {
}

private func showCustomSchedulePicker() {
if isMyKSuiteStandard {
if mailboxManager.mailbox.isMyKSuiteFree {
let eventName = type == .scheduledDraft ? "scheduledCustomDate" : "snoozeCustomDate"
matomo.track(eventWithCategory: .myKSuiteUpgradeBottomSheet, name: eventName)
isShowingMyKSuiteUpgrade = true
} else {
customSchedule = true
matomo.track(eventWithCategory: type.matomoCategory, name: "customSchedule")
isShowingCustomScheduleAlert = true
}

dismiss()
}
}

#Preview {
CustomScheduleButton(
isShowingCustomScheduleAlert: .constant(true),
isShowingMyKSuiteUpgrade: .constant(false),
type: .scheduledDraft
)
.environmentObject(PreviewHelper.sampleMailboxManager)
}
Loading
Loading