Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@ struct AddressBarState: StateType, Sendable, Equatable {
return handleDidSetTabScreenshotAction(state: state, action: action)

// Translation related actions
case ToolbarActionType.didStartTranslatingPage,
ToolbarActionType.translationCompleted,
ToolbarActionType.receivedTranslationLanguage,
ToolbarActionType.didReceiveErrorTranslating,
ToolbarActionType.didTranslationSettingsChange:
case TranslationsActionType.didStartTranslatingPage,
TranslationsActionType.translationCompleted,
TranslationsActionType.receivedTranslationLanguage,
TranslationsActionType.didReceiveErrorTranslating,
TranslationsActionType.didTranslationSettingsChange:
return handleLeadingPageChangedAction(state: state, action: action)

case ToolbarActionType.didSummarizeSettingsChange:
Expand Down Expand Up @@ -315,14 +315,14 @@ struct AddressBarState: StateType, Sendable, Equatable {

@MainActor
private static func handleLeadingPageChangedAction(state: Self, action: Action) -> Self {
guard let toolbarAction = action as? ToolbarAction else {
guard let translationsAction = action as? TranslationsAction else {
return defaultState(from: state)
}

return AddressBarState(
windowUUID: state.windowUUID,
navigationActions: state.navigationActions,
leadingPageActions: leadingPageActions(action: toolbarAction,
leadingPageActions: leadingPageActions(action: translationsAction,
addressBarState: state,
isEditing: state.isEditing),
trailingPageActions: state.trailingPageActions,
Expand All @@ -340,7 +340,7 @@ struct AddressBarState: StateType, Sendable, Equatable {
isLoading: state.isLoading,
readerModeState: state.readerModeState,
canSummarize: state.canSummarize,
translationConfiguration: toolbarAction.translationConfiguration,
translationConfiguration: translationsAction.translationConfiguration,
didStartTyping: state.didStartTyping,
isEmptySearch: state.isEmptySearch,
alternativeSearchEngine: state.alternativeSearchEngine
Expand Down Expand Up @@ -1104,30 +1104,42 @@ struct AddressBarState: StateType, Sendable, Equatable {

@MainActor
private static func leadingPageActions(
action: ToolbarAction,
action: Action,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be specialized to a ToolbarAction? Or does this method really handle other action types? If it's the latter, in my opinion we should have a guard at the top of the method that clarifies the valid action types.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added guard action is ToolbarAction || action is TranslationsAction else { return actions } at the top to explicitly document and enforce the valid action types handled by this path.

addressBarState: AddressBarState,
isEditing: Bool = false
) -> [ToolbarActionConfiguration] {
var actions = [ToolbarActionConfiguration]()

guard action is ToolbarAction || action is TranslationsAction else { return actions }

guard let toolbarState = store.state.componentState(ToolbarState.self, for: .toolbar, window: action.windowUUID),
!isEditing
else { return actions }

let isShowingNavigationToolbar = action.isShowingNavigationToolbar ?? toolbarState.isShowingNavigationToolbar
let toolbarAction = action as? ToolbarAction
let actionTranslationConfiguration = TranslationConfiguration(from: action)
let isShowingNavigationToolbar = toolbarAction?.isShowingNavigationToolbar
?? toolbarState.isShowingNavigationToolbar
let isURLDidChangeAction = action.actionType as? ToolbarActionType == .urlDidChange
let isHomepage = (isURLDidChangeAction ? action.url : toolbarState.addressToolbar.url) == nil
let isHomepage = (isURLDidChangeAction ? toolbarAction?.url : toolbarState.addressToolbar.url) == nil
let isLoadingChangeAction = action.actionType as? ToolbarActionType == .websiteLoadingStateDidChange
let isLoading = isLoadingChangeAction ? action.isLoading : addressBarState.isLoading
let hasAlternativeLocationColor = shouldUseAlternativeLocationColor(action: action)
let isLoading = isLoadingChangeAction ? toolbarAction?.isLoading : addressBarState.isLoading
let hasAlternativeLocationColor: Bool
if let toolbarAction {
hasAlternativeLocationColor = shouldUseAlternativeLocationColor(action: toolbarAction)
} else {
hasAlternativeLocationColor = toolbarState.toolbarPosition == .top
&& !toolbarState.isShowingTopTabs
&& toolbarState.isShowingNavigationToolbar
}

if !isHomepage, !isShowingNavigationToolbar {
let shareAction = shareAction(enabled: isLoading == false,
hasAlternativeLocationColor: hasAlternativeLocationColor)
actions.append(shareAction)

if let translationAction = configureTranslationIcon(
for: action,
actionTranslationConfiguration: actionTranslationConfiguration,
addressBarState: addressBarState,
isLoading: isLoading,
hasAlternativeLocationColor: hasAlternativeLocationColor
Expand All @@ -1140,7 +1152,7 @@ struct AddressBarState: StateType, Sendable, Equatable {
actions.append(shareAction)

if let translationAction = configureTranslationIcon(
for: action,
actionTranslationConfiguration: actionTranslationConfiguration,
addressBarState: addressBarState,
isLoading: isLoading,
hasAlternativeLocationColor: hasAlternativeLocationColor
Expand All @@ -1155,7 +1167,7 @@ struct AddressBarState: StateType, Sendable, Equatable {
// Checks whether we should show the translation icon based on the translation configuration
// state and setups up the configuration for the translation icon on the toolbar (for iPad and iPhone)
private static func configureTranslationIcon(
for action: ToolbarAction,
actionTranslationConfiguration: TranslationConfiguration?,
addressBarState: AddressBarState,
isLoading: Bool?,
hasAlternativeLocationColor: Bool
Expand All @@ -1167,13 +1179,13 @@ struct AddressBarState: StateType, Sendable, Equatable {
// When the action explicitly provides a config, use it as the authority (e.g. settings toggle).
// Only fall back to state when the action carries no config.
let shouldShowTranslationIcon: Bool
if let actionConfig = action.translationConfiguration {
if let actionConfig = actionTranslationConfiguration {
shouldShowTranslationIcon = actionConfig.isTranslationFeatureEnabled
} else {
shouldShowTranslationIcon = addressBarState.translationConfiguration?.isTranslationFeatureEnabled ?? false
}
guard shouldShowTranslationIcon else { return nil }
let iconState = action.translationConfiguration?.state ?? addressBarState.translationConfiguration?.state
let iconState = actionTranslationConfiguration?.state ?? addressBarState.translationConfiguration?.state
guard let iconState else { return nil }
return translateAction(
enabled: isLoading == false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,6 @@ enum ToolbarActionType: ActionType {
case didStartTyping
case translucencyDidChange
case navigationMiddleButtonDidChange
// Translations related actions that are needed to associate with the toolbar
// due to how our leadingPageActions are tied to ToolbarActions
case didStartTranslatingPage
case translationCompleted
case receivedTranslationLanguage
case didReceiveErrorTranslating
case didTranslationSettingsChange
}

struct ToolbarMiddlewareAction: Action {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ struct ToolbarState: ScreenState, Sendable {
ToolbarActionType.animationStateChanged, ToolbarActionType.translucencyDidChange,
ToolbarActionType.scrollAlphaNeedsUpdate, ToolbarActionType.readerModeStateChanged,
ToolbarActionType.navigationMiddleButtonDidChange,
ToolbarActionType.didStartTranslatingPage,
ToolbarActionType.translationCompleted,
ToolbarActionType.receivedTranslationLanguage,
ToolbarActionType.didReceiveErrorTranslating,
ToolbarActionType.didTranslationSettingsChange,
TranslationsActionType.didStartTranslatingPage,
TranslationsActionType.translationCompleted,
TranslationsActionType.receivedTranslationLanguage,
TranslationsActionType.didReceiveErrorTranslating,
TranslationsActionType.didTranslationSettingsChange,
ToolbarActionType.didSummarizeSettingsChange:
return handleToolbarUpdates(state: state, action: action)

Expand Down Expand Up @@ -234,27 +234,32 @@ struct ToolbarState: ScreenState, Sendable {

@MainActor
private static func handleToolbarUpdates(state: Self, action: Action) -> ToolbarState {
guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) }
// Both `ToolbarAction` (lifecycle) and `TranslationsAction` (translation events) reach this
// handler — translation actions carry only `isTranslationsEnabled` + payload, so every other
// ToolbarAction-only field falls back to state when the action is not a `ToolbarAction`.
let toolbarAction = action as? ToolbarAction
Comment on lines +237 to +240
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for context 👍

let translationsAction = action as? TranslationsAction
let actionIsTranslationsEnabled = toolbarAction?.isTranslationsEnabled ?? translationsAction?.isTranslationsEnabled

return ToolbarState(
windowUUID: state.windowUUID,
toolbarPosition: state.toolbarPosition,
toolbarLayout: state.toolbarLayout,
tabTrayButtonStyle: state.tabTrayButtonStyle,
isPrivateMode: toolbarAction.isPrivate ?? state.isPrivateMode,
addressToolbar: AddressBarState.reducer(state.addressToolbar, toolbarAction),
navigationToolbar: NavigationBarState.reducer(state.navigationToolbar, toolbarAction),
isShowingNavigationToolbar: toolbarAction.isShowingNavigationToolbar ?? state.isShowingNavigationToolbar,
isShowingTopTabs: toolbarAction.isShowingTopTabs ?? state.isShowingTopTabs,
canGoBack: toolbarAction.canGoBack ?? state.canGoBack,
canGoForward: toolbarAction.canGoForward ?? state.canGoForward,
isPrivateMode: toolbarAction?.isPrivate ?? state.isPrivateMode,
addressToolbar: AddressBarState.reducer(state.addressToolbar, action),
navigationToolbar: NavigationBarState.reducer(state.navigationToolbar, action),
isShowingNavigationToolbar: toolbarAction?.isShowingNavigationToolbar ?? state.isShowingNavigationToolbar,
isShowingTopTabs: toolbarAction?.isShowingTopTabs ?? state.isShowingTopTabs,
canGoBack: toolbarAction?.canGoBack ?? state.canGoBack,
canGoForward: toolbarAction?.canGoForward ?? state.canGoForward,
numberOfTabs: state.numberOfTabs,
scrollAlpha: toolbarAction.scrollAlpha ?? state.scrollAlpha,
scrollAlpha: toolbarAction?.scrollAlpha ?? state.scrollAlpha,
showMenuWarningBadge: state.showMenuWarningBadge,
canShowNavigationHint: state.canShowNavigationHint,
shouldAnimate: toolbarAction.shouldAnimate ?? state.shouldAnimate,
isTranslucent: toolbarAction.isTranslucent ?? state.isTranslucent,
isTranslationsEnabled: toolbarAction.isTranslationsEnabled ?? state.isTranslationsEnabled,
shouldAnimate: toolbarAction?.shouldAnimate ?? state.shouldAnimate,
isTranslucent: toolbarAction?.isTranslucent ?? state.isTranslucent,
isTranslationsEnabled: actionIsTranslationsEnabled ?? state.isTranslationsEnabled,
previousTabScreenshot: state.previousTabScreenshot,
nextTabScreenshot: state.nextTabScreenshot
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import Common
import Redux
import Shared

// Holds the configuration / state of the translation button on the toolbar
Expand Down Expand Up @@ -76,6 +77,18 @@ struct TranslationConfiguration: Equatable, FeatureFlaggable {
self.sourceLanguage = sourceLanguage
}

init?(from action: Action) {
if let config = (action as? ToolbarAction)?.translationConfiguration {
self = config
return
}
if let config = (action as? TranslationsAction)?.translationConfiguration {
self = config
return
}
return nil
}

var isMultiLanguageFlow: Bool {
guard featureFlagsProvider.isEnabled(.translationLanguagePicker) else { return false }
guard let stored = prefs.stringForKey(PrefsKeys.Settings.translationPreferredLanguages),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,11 @@ final class TranslationSettingsMiddleware {
from: "\(current)"
)
}
store.dispatch(ToolbarAction(
store.dispatch(TranslationsAction(
isTranslationsEnabled: newValue,
translationConfiguration: TranslationConfiguration(prefs: prefs, isUserSettingEnabled: newValue),
windowUUID: action.windowUUID,
actionType: ToolbarActionType.didTranslationSettingsChange
))
store.dispatch(TranslationSettingsMiddlewareAction(
isTranslationsEnabled: newValue,
windowUUID: action.windowUUID,
actionType: TranslationSettingsMiddlewareActionType.didUpdateSettings
actionType: TranslationsActionType.didTranslationSettingsChange
))
if !newValue {
resetStorageTask?.cancel()
Expand All @@ -105,10 +100,10 @@ final class TranslationSettingsMiddleware {
actionType: TranslationSettingsMiddlewareActionType.didUpdateSettings
))
if newValue {
store.dispatch(ToolbarAction(
store.dispatch(TranslationsAction(
translationConfiguration: TranslationConfiguration(prefs: prefs),
windowUUID: action.windowUUID,
actionType: ToolbarActionType.didTranslationSettingsChange
actionType: TranslationsActionType.didTranslationSettingsChange
))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,23 @@ struct TranslationSettingsState: ScreenState, Equatable {
guard action.windowUUID == .unavailable || action.windowUUID == state.windowUUID else {
return defaultState(from: state)
}
if let action = action as? TranslationSettingsMiddlewareAction {

switch action {
case let action as TranslationSettingsMiddlewareAction:
return reduceMiddlewareAction(state: state, action: action)
}
if let action = action as? TranslationSettingsViewAction {

case let action as TranslationSettingsViewAction:
return reduceViewAction(state: state, action: action)

case let action as TranslationsAction
where action.actionType as? TranslationsActionType == .didTranslationSettingsChange:
return state.copyWithUpdates(
isTranslationsEnabled: action.isTranslationsEnabled ?? state.isTranslationsEnabled
)

default:
return defaultState(from: state)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this reducer is supporting so many different action types, what do you think about writing it like this? 🤔

switch action.self {
    case is TranslationSettingsMiddlewareAction:
        // TODO...

    case is TranslationSettingsViewAction:
        // TODO...

    // etc...

    default:
        return defaultState(from: state)
}

Alternatively, we could switch over the actionType instead, like the ToolbarAction reducer.

Just a thought...!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restructured the logic from chained if let casts to a switch action { case let action as Type: ... } pattern, making the supported action types immediately visible and easier to scan.

return defaultState(from: state)
}

private static func reduceMiddlewareAction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ final class TranslationSettingsViewController: SettingsTableViewController {
guard let self else { return }
let isEnabled = self.prefs.boolForKey(PrefsKeys.Settings.translationsFeature) ?? true
store.dispatch(
ToolbarAction(
TranslationsAction(
isTranslationsEnabled: isEnabled,
translationConfiguration: TranslationConfiguration(
prefs: self.prefs,
isUserSettingEnabled: isEnabled,
state: .inactive
),
windowUUID: self.windowUUID,
actionType: ToolbarActionType.didTranslationSettingsChange
actionType: TranslationsActionType.didTranslationSettingsChange
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import Redux
struct TranslationsAction: Action {
let windowUUID: WindowUUID
let actionType: ActionType
let isTranslationsEnabled: Bool?
let translationConfiguration: TranslationConfiguration?

init(
isTranslationsEnabled: Bool? = nil,
translationConfiguration: TranslationConfiguration? = nil,
windowUUID: WindowUUID,
actionType: any ActionType,
) {
self.windowUUID = windowUUID
self.actionType = actionType
self.isTranslationsEnabled = isTranslationsEnabled
self.translationConfiguration = translationConfiguration
}
}

Expand Down Expand Up @@ -42,4 +48,9 @@ enum TranslationsActionType: ActionType {
case showAutoTranslatePrompt
case didTapEnableAutoTranslate
case didDismissAutoTranslatePrompt
case didStartTranslatingPage
case translationCompleted
case receivedTranslationLanguage
case didReceiveErrorTranslating
case didTranslationSettingsChange
}
Loading
Loading