mirror of
https://github.com/ProtonMail/ios-mail.git
synced 2026-06-14 09:54:45 +00:00
Merge branch 'main' into task/ET-4278
# Conflicts: # Modules/App/Sources/Resources/Localizable.xcstrings
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"project": "apple-mail-new",
|
||||
"locale": "c6e4f8631d24ac1b10b5a6c3f3e71f8f1cbe68e8"
|
||||
"locale": "5de0ac2b963308bb91a25e069a2ce18a605a6f40"
|
||||
}
|
||||
@@ -964,6 +964,24 @@
|
||||
"state" : "translated",
|
||||
"value" : "Required to save files in your Photos app."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Necesario para guardar archivos en tu aplicación Fotos."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nécessaire pour enregistrer des fichiers dans votre application Photos."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "お客様のフォトアプリにファイルを保存するために必要です。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,42 @@
|
||||
"state" : "new",
|
||||
"value" : "%1$@ of %2$@"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ de %2$@"
|
||||
}
|
||||
},
|
||||
"es-419" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ de %2$@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ De %2$@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%2$@ の中の%1$@"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ van %2$@"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ av %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5110,6 +5146,112 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@"
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Conversación movida al archivo."
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Conversaciones movidas al archivo."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@."
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "La conversation a été déplacée dans les Archives"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Les conversations ont été déplacées dans les Archives."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@"
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "会話をアーカイブに移動しました。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@"
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Konversation flyttad till arkiv."
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Konversationer flyttade till arkiv."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10565,6 +10707,112 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@"
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mensaje movido al archivo."
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mensajes movidos al Archivo."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@."
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Le message a été déplacé dans les Archives."
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Les messages ont été déplacés dans les Archives."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@"
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "メッセージをアーカイブに移動しました。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%#@arg1@"
|
||||
},
|
||||
"substitutions" : {
|
||||
"arg1" : {
|
||||
"argNum" : 1,
|
||||
"formatSpecifier" : "lld",
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Meddelande flyttat till arkiv."
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Meddelanden flyttade till arkiv."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import InboxCore
|
||||
import proton_app_uniffi
|
||||
|
||||
@@ -59,6 +60,7 @@ final class MailSettingsLiveQuery: MailSettingLiveQuerying {
|
||||
.map(keyPath)
|
||||
.removeDuplicates()
|
||||
.dropFirst()
|
||||
.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -21,15 +21,15 @@ import proton_app_uniffi
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func mailboxActionBar(
|
||||
state: MailboxActionBarState,
|
||||
availableActions: AvailableMailboxActionBarActions,
|
||||
func listActionsToolbar(
|
||||
state: ListActionsToolbarState,
|
||||
availableActions: AvailableListToolbarActions,
|
||||
itemTypeForActionBar: MailboxItemType,
|
||||
mailUserSession: MailUserSession,
|
||||
selectedItems: Binding<Set<MailboxSelectedItem>>
|
||||
) -> some View {
|
||||
modifier(
|
||||
MailboxActionBarViewModifier(
|
||||
ListActionBarViewModifier(
|
||||
state: state,
|
||||
availableActions: availableActions,
|
||||
itemTypeForActionBar: itemTypeForActionBar,
|
||||
@@ -39,13 +39,13 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
private struct ListActionBarViewModifier: ViewModifier {
|
||||
@Binding var selectedItems: Set<MailboxSelectedItem>
|
||||
@EnvironmentObject var mailbox: Mailbox
|
||||
@EnvironmentObject var toastStateStore: ToastStateStore
|
||||
private let state: MailboxActionBarState
|
||||
private let state: ListActionsToolbarState
|
||||
private let itemTypeForActionBar: MailboxItemType
|
||||
private let availableActions: AvailableMailboxActionBarActions
|
||||
private let availableActions: AvailableListToolbarActions
|
||||
private let deleteActions: DeleteActions
|
||||
private let moveToActions: MoveToActions
|
||||
private let mailUserSession: MailUserSession
|
||||
@@ -53,8 +53,8 @@ private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
private let readActionPerformerActions: ReadActionPerformerActions
|
||||
|
||||
init(
|
||||
state: MailboxActionBarState,
|
||||
availableActions: AvailableMailboxActionBarActions,
|
||||
state: ListActionsToolbarState,
|
||||
availableActions: AvailableListToolbarActions,
|
||||
starActionPerformerActions: StarActionPerformerActions = .productionInstance,
|
||||
readActionPerformerActions: ReadActionPerformerActions = .productionInstance,
|
||||
deleteActions: DeleteActions = .productionInstance,
|
||||
@@ -76,7 +76,7 @@ private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
StoreView(
|
||||
store: MailboxActionBarStateStore(
|
||||
store: ListActionsToolbarStore(
|
||||
state: state,
|
||||
availableActions: availableActions,
|
||||
starActionPerformerActions: starActionPerformerActions,
|
||||
@@ -96,11 +96,11 @@ private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
.bottomToolbarStyle()
|
||||
.onChange(of: selectedItems) { oldValue, newValue in
|
||||
if oldValue != newValue {
|
||||
store.handle(action: .mailboxItemsSelectionUpdated(ids: selectedItemsIDs))
|
||||
store.handle(action: .listItemsSelectionUpdated(ids: selectedItemsIDs))
|
||||
}
|
||||
}
|
||||
.onLoad {
|
||||
store.handle(action: .mailboxItemsSelectionUpdated(ids: selectedItemsIDs))
|
||||
store.handle(action: .listItemsSelectionUpdated(ids: selectedItemsIDs))
|
||||
}
|
||||
.labelAsSheet(
|
||||
mailbox: { mailbox },
|
||||
@@ -114,7 +114,7 @@ private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
navigation: { _ in }
|
||||
)
|
||||
.sheet(item: store.binding(\.moreActionSheetPresented)) { state in
|
||||
MailboxActionBarMoreSheet(state: state) { action in
|
||||
ListActionsToolbarMoreSheet(state: state) { action in
|
||||
store.handle(action: .moreSheetAction(action, ids: selectedItemsIDs))
|
||||
} editToolbarTapped: {
|
||||
// FIXME: - Edit toolbar action
|
||||
@@ -141,8 +141,8 @@ private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
}
|
||||
|
||||
private func toolbarContent(
|
||||
state: MailboxActionBarState,
|
||||
store: MailboxActionBarStateStore
|
||||
state: ListActionsToolbarState,
|
||||
store: ListActionsToolbarStore
|
||||
) -> some ToolbarContent {
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
HStack {
|
||||
@@ -151,7 +151,7 @@ private struct MailboxActionBarViewModifier: ViewModifier {
|
||||
Spacer()
|
||||
}
|
||||
Button(action: { store.handle(action: .actionSelected(action, ids: selectedItemsIDs)) }) {
|
||||
action.displayData.icon
|
||||
action.displayData.image
|
||||
.foregroundStyle(DS.Color.Icon.weak)
|
||||
}
|
||||
.accessibilityIdentifier(MailboxActionBarViewIdentifiers.button(index: index))
|
||||
+11
-11
@@ -17,12 +17,12 @@
|
||||
|
||||
import proton_app_uniffi
|
||||
|
||||
struct MailboxActionBarActionsProvider {
|
||||
let availableActions: AvailableMailboxActionBarActions
|
||||
struct ListActionsToolbarActionsProvider {
|
||||
let availableActions: AvailableListToolbarActions
|
||||
let itemTypeForActionBar: MailboxItemType
|
||||
let mailbox: Mailbox
|
||||
|
||||
func actions(forItemsWith ids: [ID]) async -> AllBottomBarMessageActions {
|
||||
func actions(forItemsWith ids: [ID]) async -> AllListActions {
|
||||
switch itemTypeForActionBar {
|
||||
case .message:
|
||||
try! await availableActions.message(mailbox, ids).get()
|
||||
@@ -32,21 +32,21 @@ struct MailboxActionBarActionsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
struct AvailableMailboxActionBarActions {
|
||||
let message: MessageBottomBarActionsProvider
|
||||
let conversation: ConversationBottomBarActionsProvider
|
||||
struct AvailableListToolbarActions {
|
||||
let message: MessageListActionsToolbarActionsProvider
|
||||
let conversation: ConversationListActionsToolbarActionsProvider
|
||||
}
|
||||
|
||||
extension AvailableMailboxActionBarActions {
|
||||
extension AvailableListToolbarActions {
|
||||
|
||||
static var productionInstance: Self {
|
||||
.init(
|
||||
message: allAvailableBottomBarActionsForMessages,
|
||||
conversation: allAvailableBottomBarActionsForConversations
|
||||
message: allAvailableListActionsForMessages,
|
||||
conversation: allAvailableListActionsForConversations
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typealias MessageBottomBarActionsProvider = (Mailbox, [Id]) async -> AllAvailableBottomBarActionsForMessagesResult
|
||||
typealias ConversationBottomBarActionsProvider = (Mailbox, [Id]) async -> AllAvailableBottomBarActionsForConversationsResult
|
||||
typealias MessageListActionsToolbarActionsProvider = (Mailbox, [Id]) async -> AllAvailableListActionsForMessagesResult
|
||||
typealias ConversationListActionsToolbarActionsProvider = (Mailbox, [Id]) async -> AllAvailableListActionsForConversationsResult
|
||||
+10
-15
@@ -15,18 +15,19 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import proton_app_uniffi
|
||||
import InboxCoreUI
|
||||
import InboxDesignSystem
|
||||
import SwiftUI
|
||||
|
||||
struct MailboxActionBarMoreSheet: View {
|
||||
let state: MailboxActionBarMoreSheetState
|
||||
let actionTapped: (BottomBarAction) -> Void
|
||||
struct ListActionsToolbarMoreSheet: View {
|
||||
let state: ListActionsToolbarMoreSheetState
|
||||
let actionTapped: (ListActions) -> Void
|
||||
let editToolbarTapped: () -> Void
|
||||
|
||||
init(
|
||||
state: MailboxActionBarMoreSheetState,
|
||||
actionTapped: @escaping (BottomBarAction) -> Void,
|
||||
state: ListActionsToolbarMoreSheetState,
|
||||
actionTapped: @escaping (ListActions) -> Void,
|
||||
editToolbarTapped: @escaping () -> Void
|
||||
) {
|
||||
self.state = state
|
||||
@@ -54,11 +55,11 @@ struct MailboxActionBarMoreSheet: View {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func section(content: [BottomBarAction]) -> some View {
|
||||
private func section(content: [ListActions]) -> some View {
|
||||
ActionSheetSection {
|
||||
ForEachLast(collection: content) { action, isLast in
|
||||
ActionSheetImageButton(
|
||||
displayData: action.actionDisplayData,
|
||||
displayData: action.displayData,
|
||||
displayBottomSeparator: !isLast,
|
||||
action: { actionTapped(action) }
|
||||
)
|
||||
@@ -79,15 +80,9 @@ struct MailboxActionBarMoreSheet: View {
|
||||
|
||||
}
|
||||
|
||||
private extension BottomBarAction {
|
||||
var actionDisplayData: ActionDisplayData {
|
||||
.init(title: displayData.name.unsafelyUnwrapped, image: displayData.icon)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MailboxActionBarMoreSheet(
|
||||
state: MailboxActionBarMoreSheetPreviewProvider.state(),
|
||||
ListActionsToolbarMoreSheet(
|
||||
state: ListActionsToolbarMoreSheetPreviewProvider.state(),
|
||||
actionTapped: { _ in },
|
||||
editToolbarTapped: {}
|
||||
)
|
||||
+4
-4
@@ -17,9 +17,9 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum MailboxActionBarMoreSheetPreviewProvider {
|
||||
enum ListActionsToolbarMoreSheetPreviewProvider {
|
||||
|
||||
static func state() -> MailboxActionBarMoreSheetState {
|
||||
static func state() -> ListActionsToolbarMoreSheetState {
|
||||
.init(
|
||||
selectedItemsIDs: [.init(value: 1), .init(value: 2), .init(value: 3)],
|
||||
bottomBarActions: [
|
||||
@@ -27,12 +27,12 @@ enum MailboxActionBarMoreSheetPreviewProvider {
|
||||
.moveToSystemFolder(.init(localId: .init(value: 4), name: .archive)),
|
||||
.moveToSystemFolder(.init(localId: .init(value: 5), name: .inbox)),
|
||||
.moveToSystemFolder(.init(localId: .init(value: 6), name: .trash)),
|
||||
.star
|
||||
.star,
|
||||
],
|
||||
moreSheetOnlyActions: [
|
||||
.labelAs,
|
||||
.moveTo,
|
||||
.moveToSystemFolder(.init(localId: .init(value: 7), name: .spam))
|
||||
.moveToSystemFolder(.init(localId: .init(value: 7), name: .spam)),
|
||||
]
|
||||
)
|
||||
}
|
||||
+14
-14
@@ -20,10 +20,10 @@ import InboxCoreUI
|
||||
import proton_app_uniffi
|
||||
import SwiftUI
|
||||
|
||||
final class MailboxActionBarStateStore: StateStore {
|
||||
@Published var state: MailboxActionBarState
|
||||
final class ListActionsToolbarStore: StateStore {
|
||||
@Published var state: ListActionsToolbarState
|
||||
|
||||
private let actionsProvider: MailboxActionBarActionsProvider
|
||||
private let actionsProvider: ListActionsToolbarActionsProvider
|
||||
private let starActionPerformer: StarActionPerformer
|
||||
private let readActionPerformer: ReadActionPerformer
|
||||
private let deleteActionsPerformer: DeleteActionPerformer
|
||||
@@ -33,8 +33,8 @@ final class MailboxActionBarStateStore: StateStore {
|
||||
private let mailUserSession: MailUserSession
|
||||
|
||||
init(
|
||||
state: MailboxActionBarState,
|
||||
availableActions: AvailableMailboxActionBarActions,
|
||||
state: ListActionsToolbarState,
|
||||
availableActions: AvailableListToolbarActions,
|
||||
starActionPerformerActions: StarActionPerformerActions,
|
||||
readActionPerformerActions: ReadActionPerformerActions,
|
||||
deleteActions: DeleteActions,
|
||||
@@ -65,9 +65,9 @@ final class MailboxActionBarStateStore: StateStore {
|
||||
self.toastStateStore = toastStateStore
|
||||
}
|
||||
|
||||
func handle(action: MailboxActionBarAction) {
|
||||
func handle(action: ListActionsToolbarAction) {
|
||||
switch action {
|
||||
case .mailboxItemsSelectionUpdated(let ids):
|
||||
case .listItemsSelectionUpdated(let ids):
|
||||
fetchAvailableBottomBarActions(for: ids)
|
||||
case .actionSelected(let action, let ids):
|
||||
handle(action: action, ids: ids)
|
||||
@@ -84,10 +84,10 @@ final class MailboxActionBarStateStore: StateStore {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func handle(action: BottomBarActions, ids: [ID]) {
|
||||
private func handle(action: ListActions, ids: [ID]) {
|
||||
switch action {
|
||||
case .more:
|
||||
let moreActionSheetState = MailboxActionBarMoreSheetState(
|
||||
let moreActionSheetState = ListActionsToolbarMoreSheetState(
|
||||
selectedItemsIDs: ids,
|
||||
bottomBarActions: state.bottomBarActions.moreActionFiltered,
|
||||
moreSheetOnlyActions: state.moreSheetOnlyActions
|
||||
@@ -118,7 +118,7 @@ final class MailboxActionBarStateStore: StateStore {
|
||||
dismissMoreActionSheet()
|
||||
readActionPerformer.markAsUnread(itemsWithIDs: ids, itemType: itemTypeForActionBar)
|
||||
case .permanentDelete:
|
||||
let keyPath: WritableKeyPath<MailboxActionBarState, AlertModel?> =
|
||||
let keyPath: WritableKeyPath<ListActionsToolbarState, AlertModel?> =
|
||||
state.moreActionSheetPresented != nil ? \.moreDeleteConfirmationAlert : \.deleteConfirmationAlert
|
||||
let alert: AlertModel = .deleteConfirmation(
|
||||
itemsCount: ids.count,
|
||||
@@ -190,11 +190,11 @@ final class MailboxActionBarStateStore: StateStore {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateActions(actions: AllBottomBarMessageActions) {
|
||||
private func updateActions(actions: AllListActions) {
|
||||
state =
|
||||
state
|
||||
.copy(\.bottomBarActions, to: actions.visibleBottomBarActions)
|
||||
.copy(\.moreSheetOnlyActions, to: actions.hiddenBottomBarActions)
|
||||
.copy(\.bottomBarActions, to: actions.visibleListActions)
|
||||
.copy(\.moreSheetOnlyActions, to: actions.hiddenListActions)
|
||||
}
|
||||
|
||||
private func dismissMoreActionSheet() {
|
||||
@@ -230,7 +230,7 @@ final class MailboxActionBarStateStore: StateStore {
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == BottomBarActions {
|
||||
private extension Array where Element == ListActions {
|
||||
|
||||
var moreActionFiltered: Self {
|
||||
filter { $0 != .more }
|
||||
+3
-3
@@ -17,16 +17,16 @@
|
||||
|
||||
import proton_app_uniffi
|
||||
|
||||
extension AllBottomBarMessageActions {
|
||||
extension AllListActions {
|
||||
|
||||
static var testData: Self {
|
||||
.init(
|
||||
hiddenBottomBarActions: [
|
||||
hiddenListActions: [
|
||||
.notSpam(.testInbox),
|
||||
.permanentDelete,
|
||||
.moveToSystemFolder(.init(localId: .init(value: 7), name: .archive))
|
||||
],
|
||||
visibleBottomBarActions: [.markRead, .star, .moveTo, .labelAs, .more]
|
||||
visibleListActions: [.markRead, .star, .moveTo, .labelAs, .more]
|
||||
)
|
||||
}
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2024 Proton Technologies AG
|
||||
//
|
||||
// This file is part of Proton Mail.
|
||||
//
|
||||
// Proton Mail 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.
|
||||
//
|
||||
// Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import proton_app_uniffi
|
||||
import InboxDesignSystem
|
||||
import SwiftUI
|
||||
|
||||
extension ListActions {
|
||||
|
||||
var displayData: ActionDisplayData {
|
||||
switch self {
|
||||
case .labelAs:
|
||||
.init(title: L10n.Action.labelAs, imageResource: DS.Icon.icTag)
|
||||
case .markRead:
|
||||
.init(title: L10n.Action.markAsRead, imageResource: DS.Icon.icEnvelopeOpen)
|
||||
case .markUnread:
|
||||
.init(title: L10n.Action.markAsUnread, imageResource: DS.Icon.icEnvelopeDot)
|
||||
case .more:
|
||||
.init(title: .empty, imageResource: DS.Icon.icThreeDotsHorizontal)
|
||||
case .moveTo:
|
||||
.init(title: L10n.Action.moveTo, imageResource: DS.Icon.icFolderArrowIn)
|
||||
case .moveToSystemFolder(let systemFolder):
|
||||
switch systemFolder.name {
|
||||
case .archive:
|
||||
.init(title: L10n.Action.moveToArchive, imageResource: DS.Icon.icArchiveBox)
|
||||
case .inbox:
|
||||
.init(title: L10n.Action.moveToInbox, imageResource: DS.Icon.icInbox)
|
||||
case .spam:
|
||||
.init(title: L10n.Action.moveToSpam, imageResource: DS.Icon.icSpam)
|
||||
case .trash:
|
||||
.init(title: L10n.Action.moveToTrash, imageResource: DS.Icon.icTrash)
|
||||
}
|
||||
case .notSpam:
|
||||
.init(title: L10n.Action.notSpam, imageResource: DS.Icon.icNotSpam)
|
||||
case .permanentDelete:
|
||||
.init(title: L10n.Action.deletePermanently, imageResource: DS.Icon.icTrashCross)
|
||||
case .star:
|
||||
.init(title: L10n.Action.star, image: Image(symbol: .star))
|
||||
case .unstar:
|
||||
.init(title: L10n.Action.unstar, image: Image(symbol: .starSlash))
|
||||
case .snooze:
|
||||
.init(title: L10n.Action.snooze, imageResource: DS.Icon.icClock)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension LocalizedStringResource {
|
||||
static var empty: Self {
|
||||
"".notLocalized.stringResource
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -15,10 +15,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
enum MailboxActionBarAction {
|
||||
case mailboxItemsSelectionUpdated(ids: [ID])
|
||||
case actionSelected(BottomBarAction, ids: [ID])
|
||||
case moreSheetAction(BottomBarAction, ids: [ID])
|
||||
import proton_app_uniffi
|
||||
|
||||
enum ListActionsToolbarAction {
|
||||
case listItemsSelectionUpdated(ids: [ID])
|
||||
case actionSelected(ListActions, ids: [ID])
|
||||
case moreSheetAction(ListActions, ids: [ID])
|
||||
case dismissLabelAsSheet
|
||||
case dismissMoveToSheet
|
||||
case alertActionTapped(DeleteConfirmationAlertAction, ids: [ID])
|
||||
+4
-3
@@ -15,12 +15,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import proton_app_uniffi
|
||||
import Foundation
|
||||
|
||||
struct MailboxActionBarMoreSheetState: Identifiable, Equatable {
|
||||
struct ListActionsToolbarMoreSheetState: Identifiable, Equatable {
|
||||
let selectedItemsIDs: [ID]
|
||||
let bottomBarActions: [BottomBarAction]
|
||||
let moreSheetOnlyActions: [BottomBarAction]
|
||||
let bottomBarActions: [ListActions]
|
||||
let moreSheetOnlyActions: [ListActions]
|
||||
|
||||
// MARK: - Identifiable
|
||||
|
||||
+6
-5
@@ -15,13 +15,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import proton_app_uniffi
|
||||
import InboxCore
|
||||
import InboxCoreUI
|
||||
|
||||
struct MailboxActionBarState: Copying, Equatable {
|
||||
var bottomBarActions: [BottomBarAction]
|
||||
var moreSheetOnlyActions: [BottomBarAction]
|
||||
var moreActionSheetPresented: MailboxActionBarMoreSheetState?
|
||||
struct ListActionsToolbarState: Copying, Equatable {
|
||||
var bottomBarActions: [ListActions]
|
||||
var moreSheetOnlyActions: [ListActions]
|
||||
var moreActionSheetPresented: ListActionsToolbarMoreSheetState?
|
||||
var labelAsSheetPresented: ActionSheetInput?
|
||||
var moveToSheetPresented: ActionSheetInput?
|
||||
var deleteConfirmationAlert: AlertModel?
|
||||
@@ -29,7 +30,7 @@ struct MailboxActionBarState: Copying, Equatable {
|
||||
var isSnoozeSheetPresented: Bool
|
||||
}
|
||||
|
||||
extension MailboxActionBarState {
|
||||
extension ListActionsToolbarState {
|
||||
static var initial: Self {
|
||||
.init(
|
||||
bottomBarActions: [],
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
// Copyright (c) 2024 Proton Technologies AG
|
||||
//
|
||||
// This file is part of Proton Mail.
|
||||
//
|
||||
// Proton Mail 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.
|
||||
//
|
||||
// Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import InboxDesignSystem
|
||||
import SwiftUI
|
||||
|
||||
extension BottomBarAction {
|
||||
|
||||
var displayData: MailboxActionBarActionDisplayData {
|
||||
switch self {
|
||||
case .labelAs:
|
||||
.init(imageResource: DS.Icon.icTag, name: L10n.Action.labelAs)
|
||||
case .markRead:
|
||||
.init(imageResource: DS.Icon.icEnvelopeOpen, name: L10n.Action.markAsRead)
|
||||
case .markUnread:
|
||||
.init(imageResource: DS.Icon.icEnvelopeDot, name: L10n.Action.markAsUnread)
|
||||
case .more:
|
||||
.init(imageResource: DS.Icon.icThreeDotsHorizontal, name: nil)
|
||||
case .moveTo:
|
||||
.init(imageResource: DS.Icon.icFolderArrowIn, name: L10n.Action.moveTo)
|
||||
case .moveToSystemFolder(let systemFolder):
|
||||
switch systemFolder.name {
|
||||
case .archive:
|
||||
.init(imageResource: DS.Icon.icArchiveBox, name: L10n.Action.moveToArchive)
|
||||
case .inbox:
|
||||
.init(imageResource: DS.Icon.icInbox, name: L10n.Action.moveToInbox)
|
||||
case .spam:
|
||||
.init(imageResource: DS.Icon.icSpam, name: L10n.Action.moveToSpam)
|
||||
case .trash:
|
||||
.init(imageResource: DS.Icon.icTrash, name: L10n.Action.moveToTrash)
|
||||
}
|
||||
case .notSpam:
|
||||
.init(imageResource: DS.Icon.icNotSpam, name: L10n.Action.notSpam)
|
||||
case .permanentDelete:
|
||||
.init(imageResource: DS.Icon.icTrashCross, name: L10n.Action.deletePermanently)
|
||||
case .star:
|
||||
.init(icon: Image(symbol: .star), name: L10n.Action.star)
|
||||
case .unstar:
|
||||
.init(icon: Image(symbol: .starSlash), name: L10n.Action.unstar)
|
||||
case .snooze:
|
||||
.init(imageResource: DS.Icon.icClock, name: L10n.Action.snooze)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension MailboxActionBarActionDisplayData {
|
||||
|
||||
init(imageResource: ImageResource, name: LocalizedStringResource?) {
|
||||
self.init(icon: Image(imageResource), name: name)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2024 Proton Technologies AG
|
||||
//
|
||||
// This file is part of Proton Mail.
|
||||
//
|
||||
// Proton Mail 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.
|
||||
//
|
||||
// Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import proton_app_uniffi
|
||||
|
||||
@available(*, deprecated, message: "Use BottomBarActions directly or rename the uniffi enum")
|
||||
typealias BottomBarAction = BottomBarActions
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2024 Proton Technologies AG
|
||||
//
|
||||
// This file is part of Proton Mail.
|
||||
//
|
||||
// Proton Mail 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.
|
||||
//
|
||||
// Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MailboxActionBarActionDisplayData {
|
||||
let icon: Image
|
||||
let name: LocalizedStringResource?
|
||||
|
||||
init(icon: Image, name: LocalizedStringResource?) {
|
||||
self.icon = icon
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ final class ConversationDetailModel: Sendable, ObservableObject {
|
||||
@Published private(set) var mailbox: Mailbox?
|
||||
@Published private(set) var conversationID: ID?
|
||||
@Published private(set) var isStarred: Bool
|
||||
@Published private(set) var bottomBarActions: [BottomBarActions] = []
|
||||
@Published private(set) var bottomBarActions: [ListActions] = []
|
||||
@Published var actionSheets: MailboxActionSheetsState = .initial()
|
||||
@Published var editScheduledMessageConfirmationAlert: AlertModel?
|
||||
@Published var deleteConfirmationAlert: AlertModel?
|
||||
@@ -171,7 +171,7 @@ final class ConversationDetailModel: Sendable, ObservableObject {
|
||||
isStarred ? unstarConversation() : starConversation()
|
||||
}
|
||||
|
||||
func handleConversation(action: BottomBarActions, toastStateStore: ToastStateStore, goBack: @escaping () -> Void) {
|
||||
func handleConversation(action: ListActions, toastStateStore: ToastStateStore, goBack: @escaping () -> Void) {
|
||||
let conversationID = conversationID.unsafelyUnwrapped
|
||||
switch action {
|
||||
case .labelAs:
|
||||
@@ -527,9 +527,9 @@ extension ConversationDetailModel {
|
||||
|
||||
bottomBarActions =
|
||||
try! await dependencies
|
||||
.bottomBarConversationActionsProvider(mailbox, [conversationID])
|
||||
.conversationListActionsToolbarActionsProvider(mailbox, [conversationID])
|
||||
.get()
|
||||
.visibleBottomBarActions
|
||||
.visibleListActions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,14 +553,14 @@ extension ConversationDetailModel {
|
||||
|
||||
struct Dependencies {
|
||||
let appContext: AppContext
|
||||
let bottomBarConversationActionsProvider: ConversationBottomBarActionsProvider
|
||||
let conversationListActionsToolbarActionsProvider: ConversationListActionsToolbarActionsProvider
|
||||
|
||||
init(
|
||||
appContext: AppContext = .shared,
|
||||
bottomBarConversationActionsProvider: @escaping ConversationBottomBarActionsProvider = allAvailableBottomBarActionsForConversations
|
||||
conversationListActionsToolbarActionsProvider: @escaping ConversationListActionsToolbarActionsProvider = allAvailableListActionsForConversations
|
||||
) {
|
||||
self.appContext = appContext
|
||||
self.bottomBarConversationActionsProvider = bottomBarConversationActionsProvider
|
||||
self.conversationListActionsToolbarActionsProvider = conversationListActionsToolbarActionsProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ struct ConversationDetailScreen: View {
|
||||
goBack: { navigationPath.removeLast() }
|
||||
)
|
||||
}) {
|
||||
action.displayData.icon
|
||||
action.displayData.image
|
||||
.foregroundStyle(DS.Color.Icon.weak)
|
||||
}
|
||||
.accessibilityIdentifier(MailboxActionBarViewIdentifiers.button(index: index))
|
||||
|
||||
@@ -56,7 +56,7 @@ struct MailboxItemsListView<EmptyView: View>: View {
|
||||
.animation(.none, value: selectionState.hasItems)
|
||||
.toolbar(selectionState.hasItems ? .visible : .hidden, for: .bottomBar)
|
||||
.animation(.default, value: selectionState.hasItems)
|
||||
.mailboxActionBar(
|
||||
.listActionsToolbar(
|
||||
state: .initial,
|
||||
availableActions: .productionInstance,
|
||||
itemTypeForActionBar: config.itemTypeForActionBar,
|
||||
|
||||
+4
-4
@@ -20,16 +20,16 @@ import InboxSnapshotTesting
|
||||
import InboxTesting
|
||||
|
||||
@MainActor
|
||||
class MailboxActionBarMoreSheetSnapshotTests: BaseTestCase {
|
||||
class ListActionsToolbarMoreSheetSnapshotTests: BaseTestCase {
|
||||
|
||||
func testMailboxActionBarMoreSheetLayoutsCorrectly() {
|
||||
CustomizeToolbarsFlag.$isVisible.withValue(true) {
|
||||
let sut = MailboxActionBarMoreSheet(
|
||||
state: MailboxActionBarMoreSheetPreviewProvider.state(),
|
||||
let sut = ListActionsToolbarMoreSheet(
|
||||
state: ListActionsToolbarMoreSheetPreviewProvider.state(),
|
||||
actionTapped: { _ in },
|
||||
editToolbarTapped: {}
|
||||
)
|
||||
assertSnapshotsOnIPhoneX(of: sut, named: "mailbox_action_bar_more_sheet")
|
||||
assertSnapshotsOnIPhoneX(of: sut, named: "list_actions_toolbar_more_sheet")
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
+17
-17
@@ -21,13 +21,13 @@ import InboxTesting
|
||||
import proton_app_uniffi
|
||||
import XCTest
|
||||
|
||||
class MailboxActionBarStateStoreTests: BaseTestCase {
|
||||
class ListActionsToolbarStoreTests: BaseTestCase {
|
||||
|
||||
var sut: MailboxActionBarStateStore!
|
||||
var sut: ListActionsToolbarStore!
|
||||
var invokedAvailableMessageActionsWithIDs: [[ID]]!
|
||||
var stubbedAvailableMessageActions: AllBottomBarMessageActions!
|
||||
var stubbedAvailableMessageActions: AllListActions!
|
||||
var invokedAvailableConversationActionsWithIDs: [[ID]]!
|
||||
var stubbedAvailableConversationActions: AllBottomBarMessageActions!
|
||||
var stubbedAvailableConversationActions: AllListActions!
|
||||
var starActionPerformerActionsSpy: StarActionPerformerActionsSpy!
|
||||
var readActionPerformerActionsSpy: ReadActionPerformerActionsSpy!
|
||||
var deleteActionsSpy: DeleteActionsSpy!
|
||||
@@ -60,16 +60,16 @@ class MailboxActionBarStateStoreTests: BaseTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testState_WhenMailboxItemsSelectionIsUpdatedInMessageMode_ItReturnsCorrectState() {
|
||||
func testState_WhenListItemsSelectionIsUpdatedInMessageMode_ItReturnsCorrectState() {
|
||||
sut = makeSUT(viewMode: .messages)
|
||||
stubbedAvailableMessageActions = .init(
|
||||
hiddenBottomBarActions: [.labelAs, .markRead],
|
||||
visibleBottomBarActions: [.notSpam(.testInbox)]
|
||||
hiddenListActions: [.labelAs, .markRead],
|
||||
visibleListActions: [.notSpam(.testInbox)]
|
||||
)
|
||||
|
||||
let ids: [ID] = [.init(value: 11)]
|
||||
|
||||
sut.handle(action: .mailboxItemsSelectionUpdated(ids: ids))
|
||||
sut.handle(action: .listItemsSelectionUpdated(ids: ids))
|
||||
|
||||
XCTAssertEqual(invokedAvailableMessageActionsWithIDs.count, 1)
|
||||
XCTAssertEqual(invokedAvailableConversationActionsWithIDs.count, 0)
|
||||
@@ -83,15 +83,15 @@ class MailboxActionBarStateStoreTests: BaseTestCase {
|
||||
))
|
||||
}
|
||||
|
||||
func testState_WhenMailboxItemsSelectionIsUpdatedInConversationModel_ItReturnsCorrectState() {
|
||||
func testState_WhenListItemsSelectionIsUpdatedInConversationModel_ItReturnsCorrectState() {
|
||||
sut = makeSUT(viewMode: .conversations)
|
||||
stubbedAvailableConversationActions = .init(
|
||||
hiddenBottomBarActions: [.notSpam(.testInbox), .permanentDelete],
|
||||
visibleBottomBarActions: [.more]
|
||||
hiddenListActions: [.notSpam(.testInbox), .permanentDelete],
|
||||
visibleListActions: [.more]
|
||||
)
|
||||
let ids: [ID] = [.init(value: 22)]
|
||||
|
||||
sut.handle(action: .mailboxItemsSelectionUpdated(ids: ids))
|
||||
sut.handle(action: .listItemsSelectionUpdated(ids: ids))
|
||||
|
||||
XCTAssertEqual(invokedAvailableMessageActionsWithIDs.count, 0)
|
||||
XCTAssertEqual(invokedAvailableConversationActionsWithIDs.count, 1)
|
||||
@@ -105,10 +105,10 @@ class MailboxActionBarStateStoreTests: BaseTestCase {
|
||||
))
|
||||
}
|
||||
|
||||
func testState_WhenMailboxItemsSelectionIsUpdatedWithNoSelection_ItReturnsCorrectState() {
|
||||
func testState_WhenListItemsSelectionIsUpdatedWithNoSelection_ItReturnsCorrectState() {
|
||||
sut = makeSUT(viewMode: .messages)
|
||||
|
||||
sut.handle(action: .mailboxItemsSelectionUpdated(ids: []))
|
||||
sut.handle(action: .listItemsSelectionUpdated(ids: []))
|
||||
|
||||
XCTAssertEqual(invokedAvailableMessageActionsWithIDs.count, 0)
|
||||
}
|
||||
@@ -151,7 +151,7 @@ class MailboxActionBarStateStoreTests: BaseTestCase {
|
||||
|
||||
XCTAssertNil(sut.state.moreActionSheetPresented)
|
||||
|
||||
sut.handle(action: .mailboxItemsSelectionUpdated(ids: ids))
|
||||
sut.handle(action: .listItemsSelectionUpdated(ids: ids))
|
||||
sut.handle(action: .actionSelected(.more, ids: ids))
|
||||
|
||||
XCTAssertEqual(
|
||||
@@ -285,8 +285,8 @@ class MailboxActionBarStateStoreTests: BaseTestCase {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func makeSUT(viewMode: ViewMode) -> MailboxActionBarStateStore {
|
||||
MailboxActionBarStateStore(
|
||||
private func makeSUT(viewMode: ViewMode) -> ListActionsToolbarStore {
|
||||
ListActionsToolbarStore(
|
||||
state: .initial,
|
||||
availableActions: .init(
|
||||
message: { _, ids in
|
||||
@@ -4,8 +4,8 @@
|
||||
import Foundation
|
||||
import proton_app_uniffi
|
||||
|
||||
public extension AllAvailableBottomBarActionsForConversationsResult {
|
||||
func get() throws(ActionError) -> AllBottomBarMessageActions {
|
||||
public extension AllAvailableListActionsForConversationsResult {
|
||||
func get() throws(ActionError) -> AllListActions {
|
||||
switch self {
|
||||
case .ok(let value):
|
||||
value
|
||||
@@ -14,8 +14,8 @@ public extension AllAvailableBottomBarActionsForConversationsResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension AllAvailableBottomBarActionsForMessagesResult {
|
||||
func get() throws(ActionError) -> AllBottomBarMessageActions {
|
||||
public extension AllAvailableListActionsForMessagesResult {
|
||||
func get() throws(ActionError) -> AllListActions {
|
||||
switch self {
|
||||
case .ok(let value):
|
||||
value
|
||||
|
||||
@@ -161,7 +161,9 @@ struct ContactDetailsScreen: View {
|
||||
case .urls(let urls):
|
||||
FormList(collection: urls) { item in
|
||||
button(item: ContactFormatter.URL.formatted(from: item)) {
|
||||
store.handle(action: .openURL(urlString: item.url))
|
||||
if let urlString = item.url.urlString {
|
||||
store.handle(action: .openURL(urlString: urlString))
|
||||
}
|
||||
}
|
||||
}
|
||||
case .logos, .photos:
|
||||
@@ -207,6 +209,19 @@ private extension FormList {
|
||||
|
||||
}
|
||||
|
||||
private extension VCardUrlValue {
|
||||
|
||||
var urlString: String? {
|
||||
switch self {
|
||||
case .http(let string):
|
||||
string
|
||||
case .notHttp, .text:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactDetailsScreen(
|
||||
contact: ContactItem(
|
||||
|
||||
+12
-1
@@ -97,7 +97,7 @@ enum ContactFormatter {
|
||||
static func formatted(from vcardURL: VCardUrl) -> ContactDetailsItem {
|
||||
let label = vcardURL.urlType.humanReadable(fallback: L10n.ContactDetails.Label.url.string)
|
||||
|
||||
return .init(label: label, value: vcardURL.url, isInteractive: true)
|
||||
return .init(label: label, value: vcardURL.url.value, isInteractive: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,3 +177,14 @@ private extension String {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension VCardUrlValue {
|
||||
|
||||
var value: String {
|
||||
switch self {
|
||||
case .http(let string), .notHttp(let string), .text(let string):
|
||||
string
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ final class ContactDetailsScreenSnapshotTests {
|
||||
.languages(["french"]),
|
||||
.organizations(["CERN", "NASA"]),
|
||||
.members(["N/A"]),
|
||||
.urls([.init(url: "https://www.nasa.gov", urlType: [.work])]),
|
||||
.urls([.init(url: .http("https://www.nasa.gov"), urlType: [.work])]),
|
||||
]
|
||||
|
||||
assertSnapshotsOnIPhoneX(of: makeSUT(items: items))
|
||||
|
||||
+5
-5
@@ -171,11 +171,11 @@ struct ContactFormatterTests {
|
||||
arguments:
|
||||
zip(
|
||||
[
|
||||
VCardUrl(url: "https://swissbank.ch", urlType: [.work]),
|
||||
VCardUrl(url: "https://voice.example", urlType: [.voice]),
|
||||
VCardUrl(url: "https://personal.blog", urlType: [.home]),
|
||||
VCardUrl(url: "https://example.org", urlType: []),
|
||||
VCardUrl(url: "https://custom.link", urlType: [.string("GitHub")]),
|
||||
VCardUrl(url: .http("https://swissbank.ch"), urlType: [.work]),
|
||||
VCardUrl(url: .http("https://voice.example"), urlType: [.voice]),
|
||||
VCardUrl(url: .http("https://personal.blog"), urlType: [.home]),
|
||||
VCardUrl(url: .http("https://example.org"), urlType: []),
|
||||
VCardUrl(url: .http("https://custom.link"), urlType: [.string("GitHub")]),
|
||||
],
|
||||
[
|
||||
ContactDetailsItem(label: "Work", value: "https://swissbank.ch", isInteractive: true),
|
||||
|
||||
@@ -44,6 +44,26 @@ public extension ChallengeLoaderGetResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension ChallengeLoaderPostResult {
|
||||
func get() throws(ProtonError) -> ChallengeLoaderResponse {
|
||||
switch self {
|
||||
case .ok(let value):
|
||||
value
|
||||
case .error(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension ChallengeLoaderPutResult {
|
||||
func get() throws(ProtonError) -> ChallengeLoaderResponse {
|
||||
switch self {
|
||||
case .ok(let value):
|
||||
value
|
||||
case .error(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension CreateMailSessionResult {
|
||||
func get() throws(UserSessionError) -> MailSession {
|
||||
switch self {
|
||||
@@ -54,6 +74,36 @@ public extension CreateMailSessionResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension CustomSettingsMobileSignatureResult {
|
||||
func get() throws(ProtonError) -> MobileSignature {
|
||||
switch self {
|
||||
case .ok(let value):
|
||||
value
|
||||
case .error(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension CustomSettingsSetMobileSignatureEnabledResult {
|
||||
func get() throws(ProtonError) {
|
||||
switch self {
|
||||
case .ok:
|
||||
break
|
||||
case .error(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension CustomSettingsSetMobileSignatureResult {
|
||||
func get() throws(ProtonError) {
|
||||
switch self {
|
||||
case .ok:
|
||||
break
|
||||
case .error(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension DraftExpirationTimeResult {
|
||||
func get() throws(ProtonError) -> DraftExpirationTime {
|
||||
switch self {
|
||||
|
||||
@@ -214,13 +214,13 @@
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il vous reste %lld tentative avant la déconnexion."
|
||||
"value" : "%lld Tentative restante avant la déconnexion"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il vous reste %lld tentatives avant la déconnexion."
|
||||
"value" : "%lld tentatives restantes avant la déconnexion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,24 @@
|
||||
"state" : "new",
|
||||
"value" : "Billed at %1$@ every %2$@"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Facturado a %1$@ cada %2$@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Facturé %1$@ tous les %2$@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "毎%2$@に%1$@の支払い"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.125.1
|
||||
0.130.0
|
||||
|
||||
Reference in New Issue
Block a user